feat(web): unify worker wasm calls behind json abi
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-30 06:45:31 +08:00
parent 34801d655e
commit d101ff2d1f
34 changed files with 1775 additions and 96 deletions

View File

@@ -129,7 +129,7 @@ target_link_options(web_engine PRIVATE
"-sINITIAL_MEMORY=67108864"
"-sMAXIMUM_MEMORY=2147483648"
"-sSTACK_SIZE=8388608"
"-sEXPORTED_FUNCTIONS=['_malloc','_free','_web_engine_create','_web_engine_destroy','_web_engine_get_memory_stats','_web_engine_get_live_handles','_web_engine_get_allocated_bytes','_web_engine_open_blend','_web_engine_apply_command','_web_engine_append_library_object','_web_engine_undo','_web_engine_redo','_web_engine_get_scene_snapshot','_web_engine_get_scene_metadata','_web_engine_get_scene_geometry','_web_engine_get_scene_delta','_web_engine_get_packed_asset','_web_engine_evaluate_depsgraph','_web_engine_save_blend','_web_engine_free_buffer','_web_engine_last_error_code','_web_engine_last_error_message','_web_engine_decimate_apply']"
"-sEXPORTED_FUNCTIONS=['_malloc','_free','_web_engine_create','_web_engine_destroy','_web_engine_get_memory_stats','_web_engine_get_live_handles','_web_engine_get_allocated_bytes','_web_engine_open_blend','_web_engine_call_json','_web_engine_append_library_object','_web_engine_get_scene_metadata','_web_engine_get_scene_geometry','_web_engine_get_packed_asset','_web_engine_save_blend','_web_engine_free_buffer','_web_engine_last_error_code','_web_engine_last_error_message','_web_engine_decimate_apply']"
"-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString']"
)
if(WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH)

View File

@@ -1048,6 +1048,26 @@ void set_error(const int code, const char *message)
last_error_message = message != nullptr ? message : "unknown web engine error";
}
int write_json_result(const json &value, const uint8_t **result_data, uint32_t *result_length)
{
if (result_data == nullptr || result_length == nullptr) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, "unified JSON result output is null");
return last_error_code;
}
const std::string serialized = value.dump();
uint8_t *copy = static_cast<uint8_t *>(std::malloc(serialized.size()));
if (copy == nullptr && !serialized.empty()) {
*result_data = nullptr;
*result_length = 0;
set_error(WEB_ENGINE_BLEND_WRITE_FAILED, "unable to allocate unified JSON result");
return last_error_code;
}
if (!serialized.empty()) std::memcpy(copy, serialized.data(), serialized.size());
*result_data = copy;
*result_length = static_cast<uint32_t>(serialized.size());
return WEB_ENGINE_OK;
}
EngineState *find_engine(const int handle)
{
const auto found = engines.find(handle);
@@ -2355,6 +2375,90 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
}
}
/* Stable JSON facade for Worker-to-WASM calls. Binary-only transfers retain their
* dedicated ABI, while stateful commands and JSON reports use this entrypoint. */
EMSCRIPTEN_KEEPALIVE int web_engine_call_json(const int handle,
const uint8_t *data,
const uint32_t length,
const uint8_t **result_data,
uint32_t *result_length)
{
if (result_data != nullptr) *result_data = nullptr;
if (result_length != nullptr) *result_length = 0;
if (data == nullptr || length == 0) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, "unified JSON request is empty");
return write_json_result({{"ok", false}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
try {
const json request = json::parse(reinterpret_cast<const char *>(data),
reinterpret_cast<const char *>(data) + length);
if (!request.is_object() || !request.contains("cmd") || !request["cmd"].is_string()) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, "unified JSON request requires string cmd");
return write_json_result({{"ok", false}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
const std::string cmd = request["cmd"].get<std::string>();
const json params = request.value("params", json::object());
if (!params.is_object()) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, "unified JSON params must be an object");
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
json result;
if (cmd == "applyCommand") {
if (!params.contains("command") || !params["command"].is_object()) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, "applyCommand params.command must be an object");
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
const std::string command = params["command"].dump();
const int code = web_engine_apply_command(handle,
reinterpret_cast<const uint8_t *>(command.data()),
static_cast<uint32_t>(command.size()));
if (code != WEB_ENGINE_OK) {
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
result = {{"status", "applied"}};
}
else if (cmd == "snapshot" || cmd == "delta" || cmd == "memoryStats") {
EngineState *engine = find_engine(handle);
if (engine == nullptr) {
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
if (cmd == "snapshot") result = json::parse(engine->scene_snapshot.empty() ? empty_scene_snapshot_json() : engine->scene_snapshot);
else if (cmd == "delta") result = json::parse(engine->scene_delta.empty() ? "{\"schemaVersion\":1,\"baseRevision\":0,\"nextRevision\":0}" : engine->scene_delta);
else {
const WebEngineMemoryStats stats = web_engine_get_memory_stats();
result = {{"liveHandles", stats.live_handles}, {"allocatedBytes", stats.allocated_bytes}};
}
}
else if (cmd == "undo" || cmd == "redo") {
const int code = cmd == "undo" ? web_engine_undo(handle) : web_engine_redo(handle);
if (code != WEB_ENGINE_OK) {
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
result = {{"status", cmd}};
}
else if (cmd == "depsgraph") {
const uint8_t *report_data = nullptr;
uint32_t report_length = 0;
const int code = web_engine_evaluate_depsgraph(handle, &report_data, &report_length);
if (code != WEB_ENGINE_OK) {
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
result = json::parse(reinterpret_cast<const char *>(report_data),
reinterpret_cast<const char *>(report_data) + report_length);
}
else {
set_error(WEB_ENGINE_NOT_IMPLEMENTED, "unified JSON cmd is not implemented");
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
set_error(WEB_ENGINE_OK, "ok");
return write_json_result({{"ok", true}, {"cmd", cmd}, {"result", std::move(result)}}, result_data, result_length);
}
catch (const std::exception &exception) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, exception.what());
return write_json_result({{"ok", false}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
}
}
EMSCRIPTEN_KEEPALIVE int web_engine_append_library_object(const int handle,
const uint8_t *source_data,
const uint32_t source_length,

View File

@@ -28,6 +28,11 @@ uint32_t web_engine_get_live_handles(void);
uint32_t web_engine_get_allocated_bytes(void);
int web_engine_open_blend(int handle, const uint8_t *data, uint32_t length);
int web_engine_apply_command(int handle, const uint8_t *data, uint32_t length);
int web_engine_call_json(int handle,
const uint8_t *data,
uint32_t length,
const uint8_t **result_data,
uint32_t *result_length);
int web_engine_append_library_object(int handle,
const uint8_t *source_data,
uint32_t source_length,