Checkpoint 2: integrate native Editor, MCP, gameplay builds and standalone export
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <faset/core/json.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace faset::editor {
|
||||
struct BuildConfig {
|
||||
std::filesystem::path project_root;
|
||||
std::filesystem::path engine_root;
|
||||
std::filesystem::path build_directory;
|
||||
std::filesystem::path cache_root;
|
||||
// Separate CMake caches prevent an export from changing the active development build.
|
||||
std::string configuration{"Debug"};
|
||||
std::string export_configuration{"Release"};
|
||||
std::string cmake{"cmake"};
|
||||
std::string generator{"Ninja"};
|
||||
std::vector<std::string> configure_arguments;
|
||||
};
|
||||
struct JobStatus {
|
||||
std::string id, kind, state{"queued"}, stage{"queued"};
|
||||
double progress{};
|
||||
std::string log, error;
|
||||
Json result = Json::object();
|
||||
bool finished() const {
|
||||
return state == "succeeded" || state == "failed" || state == "cancelled";
|
||||
}
|
||||
Json json() const;
|
||||
};
|
||||
// One serialized build/cook worker per project. GUI and MCP use the same service.
|
||||
// Input scenes must be resolved authoring snapshots, independent of live runtime state.
|
||||
class BuildService {
|
||||
public:
|
||||
explicit BuildService(BuildConfig);
|
||||
~BuildService();
|
||||
BuildService(const BuildService&) = delete;
|
||||
BuildService& operator=(const BuildService&) = delete;
|
||||
void scaffold(const std::string& name, int dimension);
|
||||
std::string start_build();
|
||||
std::string start_cook(Json resolved_scene);
|
||||
// Publishes output/generations/<id>; current.json changes only after all validation succeeds.
|
||||
std::string start_export(Json resolved_scene, const std::filesystem::path& output_directory);
|
||||
JobStatus job(const std::string& id) const;
|
||||
std::vector<JobStatus> jobs() const;
|
||||
void cancel(const std::string& id);
|
||||
JobStatus wait(const std::string& id);
|
||||
const BuildConfig& config() const;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
void write_cooked_scene(const std::filesystem::path& path, const Json& resolved_scene);
|
||||
} // namespace faset::editor
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <faset/authoring/service.hpp>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace faset::editor {
|
||||
class Commands {
|
||||
public:
|
||||
using Handler = std::function<Json(const Json&)>;
|
||||
explicit Commands(authoring::AuthoringService& authoring);
|
||||
void add(std::string name, std::string description, Json input_schema, Handler handler,
|
||||
bool read_only = false);
|
||||
Json list() const;
|
||||
void remove(const std::string& name);
|
||||
Json call(const std::string& name, const Json& arguments);
|
||||
Json resolved_scene(const std::string& document) const;
|
||||
authoring::AuthoringService& authoring() {
|
||||
return authoring_;
|
||||
}
|
||||
static Json object_schema(Json properties, Json required = Json::array());
|
||||
|
||||
private:
|
||||
struct Command {
|
||||
Json descriptor;
|
||||
Handler handler;
|
||||
};
|
||||
authoring::AuthoringService& authoring_;
|
||||
std::map<std::string, Command> commands_;
|
||||
};
|
||||
} // namespace faset::editor
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <faset/editor/session.hpp>
|
||||
#include <faset/ui/ui.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace faset::editor {
|
||||
// Owns authoring presentation only. Player execution and MCP transport stay in
|
||||
// the application.
|
||||
class EditorUI {
|
||||
public:
|
||||
EditorUI(Session&, render::Renderer&, const std::filesystem::path& font,
|
||||
const std::filesystem::path& styles);
|
||||
~EditorUI();
|
||||
EditorUI(const EditorUI&) = delete;
|
||||
EditorUI& operator=(const EditorUI&) = delete;
|
||||
void frame(const std::vector<render::Event>&);
|
||||
const render::Snapshot& snapshot() const;
|
||||
ui::Context& widgets();
|
||||
const std::string& current_document() const;
|
||||
void select_document(const std::string& document);
|
||||
const std::string& selected_entity() const;
|
||||
void select_entity(const std::string& entity);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
} // namespace faset::editor
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <faset/editor/commands.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace faset::editor {
|
||||
// MCP is compiled exclusively into the Editor / headless authoring executable.
|
||||
class McpServer {
|
||||
public:
|
||||
explicit McpServer(Commands& commands) : commands_(commands) {}
|
||||
std::optional<Json> handle(const Json& message);
|
||||
Json parse_error() const;
|
||||
|
||||
private:
|
||||
Commands& commands_;
|
||||
bool initialized_ = false, ready_ = false;
|
||||
};
|
||||
class StdioTransport {
|
||||
public:
|
||||
// Nonblocking: GUI and MCP can share the same authoring session and event loop.
|
||||
std::vector<std::string> poll();
|
||||
bool closed() const noexcept {
|
||||
return closed_;
|
||||
}
|
||||
void send(const Json& message);
|
||||
|
||||
private:
|
||||
std::string buffer_;
|
||||
bool closed_ = false;
|
||||
};
|
||||
} // namespace faset::editor
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
/* Exact-build native Editor SDK. No STL types or ownership cross this ABI. */
|
||||
#include <faset/editor/sdk_build.h>
|
||||
#include <stdint.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#define FASET_EDITOR_API_VERSION 1u
|
||||
#if defined(_WIN32)
|
||||
#define FASET_PLUGIN_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define FASET_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
/* UTF-8 JSON is borrowed for the duration of a call. write() copies output in
|
||||
the receiving module. Return zero on success; errors use {code,message}. */
|
||||
typedef void (*FasetWrite)(void* receiver, const char* utf8, uint64_t length);
|
||||
typedef int (*FasetCommand)(void* user, const char* arguments_json, FasetWrite write,
|
||||
void* receiver);
|
||||
typedef struct FasetEditorHost {
|
||||
uint32_t api_version;
|
||||
uint32_t struct_size;
|
||||
const char* build_fingerprint;
|
||||
void* context;
|
||||
int (*register_command)(void* context, const char* descriptor_json, FasetCommand callback,
|
||||
void* user);
|
||||
int (*register_panel)(void* context, const char* panel_json);
|
||||
int (*invoke_command)(void* context, const char* name, const char* arguments_json,
|
||||
FasetWrite write, void* receiver);
|
||||
void (*log)(void* context, const char* utf8);
|
||||
} FasetEditorHost;
|
||||
typedef struct FasetEditorPlugin {
|
||||
uint32_t api_version;
|
||||
uint32_t struct_size;
|
||||
const char* build_fingerprint;
|
||||
void* user;
|
||||
void (*shutdown)(void* user);
|
||||
} FasetEditorPlugin;
|
||||
/* Required exported symbol. Called once at startup, after dependencies load. */
|
||||
typedef int (*FasetPluginEntry)(const FasetEditorHost* host, FasetEditorPlugin* plugin);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <faset/editor/commands.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
namespace faset::editor {
|
||||
class PluginManager {
|
||||
public:
|
||||
using Logger = std::function<void(std::string)>;
|
||||
explicit PluginManager(Commands&, Logger);
|
||||
~PluginManager();
|
||||
PluginManager(const PluginManager&) = delete;
|
||||
PluginManager& operator=(const PluginManager&) = delete;
|
||||
// Discover and validate the complete dependency graph before loading code.
|
||||
// Individual failures are reported; dependents are never loaded after one.
|
||||
void load(const std::filesystem::path& directory);
|
||||
Json status() const;
|
||||
Json panels() const;
|
||||
static std::string fingerprint();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
} // namespace faset::editor
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include <faset/assets/asset_pipeline.hpp>
|
||||
#include <faset/core/process.hpp>
|
||||
#include <faset/editor/build_service.hpp>
|
||||
#include <faset/editor/commands.hpp>
|
||||
#include <faset/editor/plugins.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace faset::editor {
|
||||
struct SessionConfig {
|
||||
std::filesystem::path project_root, engine_root, binary_directory;
|
||||
};
|
||||
class Session {
|
||||
public:
|
||||
explicit Session(SessionConfig);
|
||||
~Session();
|
||||
Session(const Session&) = delete;
|
||||
Session& operator=(const Session&) = delete;
|
||||
authoring::AuthoringService& authoring() {
|
||||
return authoring_;
|
||||
}
|
||||
Commands& commands() {
|
||||
return commands_;
|
||||
}
|
||||
void poll();
|
||||
const std::vector<std::string>& logs() const {
|
||||
return logs_;
|
||||
}
|
||||
Json project() const;
|
||||
Json plugin_panels() const {
|
||||
return plugins_ ? plugins_->panels() : Json::array();
|
||||
}
|
||||
void scaffold(const std::string& name, int dimension);
|
||||
const SessionConfig& config() const {
|
||||
return config_;
|
||||
}
|
||||
bool playing() const {
|
||||
return bool(player_);
|
||||
}
|
||||
void log(std::string message);
|
||||
|
||||
private:
|
||||
struct ImportTask;
|
||||
void register_commands();
|
||||
Json assets_list() const;
|
||||
Json jobs() const;
|
||||
Json job(const std::string& id) const;
|
||||
void load_schema(const std::filesystem::path& path);
|
||||
void launch_player(Json scene, const std::filesystem::path& executable);
|
||||
void stop_player();
|
||||
SessionConfig config_;
|
||||
authoring::AuthoringService authoring_;
|
||||
Commands commands_;
|
||||
assets::AssetPipeline assets_;
|
||||
BuildService builds_;
|
||||
std::map<std::string, std::shared_ptr<ImportTask>> imports_;
|
||||
std::vector<std::jthread> workers_;
|
||||
std::vector<std::string> logs_;
|
||||
std::map<std::string, std::string> observed_jobs_;
|
||||
std::unique_ptr<Process> player_;
|
||||
std::filesystem::path control_path_;
|
||||
std::uint64_t control_sequence_ = 0;
|
||||
std::string pending_play_job_;
|
||||
Json pending_play_scene_;
|
||||
std::unique_ptr<PluginManager> plugins_;
|
||||
};
|
||||
} // namespace faset::editor
|
||||
Reference in New Issue
Block a user