Checkpoint 2: integrate native Editor, MCP, gameplay builds and standalone export

This commit is contained in:
Emil
2026-09-18 03:40:15 +03:00
parent 5c6b24d34d
commit 999686a896
125 changed files with 16086 additions and 1714 deletions
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <faset/runtime/Runtime.hpp>
namespace beacon {
inline nlohmann::json schema() {
return {{"id", "example.beacon"},
{"name", "Beacon"},
{"version", 1},
{"fields",
{{"speed",
{{"id", "speed"},
{"name", "Rotation speed"},
{"type", "number"},
{"default", 1.0},
{"units", "rad/s"}}}}}};
}
inline void register_behavior(faset::runtime::Runtime& runtime) {
faset::runtime::Behavior behavior;
behavior.fixedUpdate = [](faset::runtime::Runtime& world, faset::runtime::EntityHandle self,
double dt) {
auto pose = world.transform(self);
pose.rotation[1] +=
world.fields(self, "example.beacon").value("speed", 1.0f) * static_cast<float>(dt);
world.setTransform(self, pose);
};
runtime.registerBehavior("example.beacon", std::move(behavior));
}
} // namespace beacon
+119
View File
@@ -0,0 +1,119 @@
#include <array>
#include <faset/editor/plugin_api.h>
#include <iomanip>
#include <nlohmann/json.hpp>
#include <random>
#include <sstream>
#include <stdexcept>
namespace {
using Json = nlohmann::json;
struct State {
const FasetEditorHost* host;
};
void collect(void* target, const char* data, uint64_t size) {
static_cast<std::string*>(target)->append(data, size);
}
Json invoke(State& state, const char* command, const Json& arguments) {
std::string output;
const auto input = arguments.dump();
const auto status =
state.host->invoke_command(state.host->context, command, input.c_str(), collect, &output);
auto result = Json::parse(output);
if (status != 0)
throw std::runtime_error(result.value("message", std::string("Editor command failed")));
return result;
}
std::string id() {
std::random_device random;
std::ostringstream stream;
stream << std::hex << std::setfill('0');
for (int i = 0; i < 4; ++i)
stream << std::setw(8) << random();
return stream.str();
}
int create(void* user, const char* input, FasetWrite write, void* receiver) {
try {
auto& state = *static_cast<State*>(user);
const auto arguments = Json::parse(input);
const auto document =
invoke(state, "faset_document_query", {{"document", arguments.at("document")}});
const auto schema = invoke(state, "faset_schema", Json::object());
bool known = false;
for (const auto& type : schema.at("types"))
if (type.at("id") == "example.beacon")
known = true;
if (!known)
throw std::runtime_error("Include Beacon.hpp in gameplay, register its schema and "
"behavior, then Build before adding a Beacon");
const auto entity = id();
Json record = {
{"id", entity},
{"name", "Beacon"},
{"parent", nullptr},
{"components",
Json::array(
{{{"id", id()},
{"type", "faset.transform"},
{"version", 1},
{"fields",
{{"position", {0, 1, 0}}, {"rotation", {0, 0, 0}}, {"scale", {1, 1, 1}}}}},
{{"id", id()},
{"type", "faset.mesh"},
{"version", 1},
{"fields",
{{"asset", ""}, {"primitive", "cube"}, {"color", {0.68, 0.55, 0.95, 1}}}}},
{{"id", id()},
{"type", "example.beacon"},
{"version", 1},
{"fields", {{"speed", 1.0}}}}})}};
const auto result =
invoke(state, "faset_scene_edit",
{{"document", document.at("id")},
{"revision", document.at("revision")},
{"operations", Json::array({{{"op", "entity.create"}, {"entity", record}}})}})
.dump();
write(receiver, result.data(), result.size());
return 0;
} catch (const std::exception& error) {
const auto output = Json{{"code", "beacon.create"}, {"message", error.what()}}.dump();
write(receiver, output.data(), output.size());
return 1;
}
}
void shutdown(void* user) {
delete static_cast<State*>(user);
}
} // namespace
extern "C" FASET_PLUGIN_EXPORT int faset_editor_plugin(const FasetEditorHost* host,
FasetEditorPlugin* plugin) {
if (!host || !plugin || host->api_version != FASET_EDITOR_API_VERSION ||
host->struct_size != sizeof(FasetEditorHost) ||
std::string(host->build_fingerprint) != FASET_EDITOR_SDK_FINGERPRINT)
return 1;
try {
auto* state = new State{host};
*plugin = {FASET_EDITOR_API_VERSION, sizeof(FasetEditorPlugin),
FASET_EDITOR_SDK_FINGERPRINT, state, shutdown};
const auto descriptor = Json{
{"name", "plugin_example_beacon_create"},
{"description", "Create a rotating Beacon using one authoring transaction."},
{"inputSchema",
{{"type", "object"},
{"properties", {{"document", {{"type", "string"}}}}},
{"required", {"document"}},
{"additionalProperties",
false}}}}.dump();
if (host->register_command(host->context, descriptor.c_str(), create, state) != 0)
return 1;
const auto panel = Json{{"id", "example.beacon.tools"},
{"title", "Beacon tools"},
{"action", "Add Beacon"},
{"command", "plugin_example_beacon_create"},
{"arguments", {{"document", "$document"}}}}
.dump();
return host->register_panel(host->context, panel.c_str());
} catch (...) {
return 1;
}
}