215 lines
9.2 KiB
C++
215 lines
9.2 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "common/Module.h"
|
|
#include "editor/Editor.h"
|
|
#include "editor/EditorProtocol.h"
|
|
#include "editor/EditorValue.h"
|
|
|
|
#include <simplesquirrel/simplesquirrel.hpp>
|
|
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
using namespace eve::editor;
|
|
|
|
TEST_CASE("editor.v2.script_discovers_plans_and_executes_registered_command") {
|
|
Editor* editor = Editor::create();
|
|
auto& commands = editor->commandService();
|
|
|
|
const CommandId commandId("test.script.place-attraction");
|
|
commands.unregisterCommand(commandId);
|
|
CommandDescriptor descriptor;
|
|
descriptor.id = commandId;
|
|
descriptor.ownerModule = "test.script";
|
|
descriptor.displayName = "Place Attraction";
|
|
descriptor.category = "Build";
|
|
|
|
bool plannerSawScriptSource = false;
|
|
int executedAmount = 0;
|
|
CHECK(commands
|
|
.registerPlannedCommand(
|
|
descriptor,
|
|
[&](const CommandRequest& request) {
|
|
plannerSawScriptSource = request.source == CommandSource::Script;
|
|
const auto* object = request.payload.getIf<EditorValue::Object>();
|
|
if (!object)
|
|
return EditorResult<CommandPlan>::error(EditorStatus::Rejected, RuleId("test.script.payload"),
|
|
"Object payload required");
|
|
const auto amount = object->find("amount");
|
|
if (amount == object->end() || !amount->second.getIf<int64_t>())
|
|
return EditorResult<CommandPlan>::error(EditorStatus::Rejected, RuleId("test.script.amount"),
|
|
"Integer amount required");
|
|
CommandPlan plan;
|
|
plan.summary = "Place an attraction from a script-authored request";
|
|
DomainOperation operation;
|
|
operation.type = "test.place";
|
|
operation.inverseType = "test.unplace";
|
|
operation.payload = request.payload;
|
|
operation.hasInverse = true;
|
|
plan.operations.push_back(std::move(operation));
|
|
return EditorResult<CommandPlan>::applied(std::move(plan));
|
|
},
|
|
[&](const CommandRequest& request, const CommandPlan& plan) {
|
|
const auto& object = *request.payload.getIf<EditorValue::Object>();
|
|
executedAmount = static_cast<int>(*object.at("amount").getIf<int64_t>());
|
|
TransactionReceipt receipt;
|
|
receipt.id = TransactionId(plan.id.value());
|
|
receipt.state = TransactionState::Committed;
|
|
receipt.beforeRevision = plan.baseRevision;
|
|
receipt.afterRevision = plan.baseRevision + 1;
|
|
receipt.authorityReceipt = "local:test.script";
|
|
return EditorResult<TransactionReceipt>::applied(std::move(receipt));
|
|
})
|
|
.isAccepted());
|
|
|
|
ssq::VM vm(1024, ssq::Libs::ALL);
|
|
eve::ModuleManager::expose(vm);
|
|
vm.run(vm.compileSource(R"(
|
|
editor <- eve.Editor();
|
|
session <- editor.newSession();
|
|
found <- false;
|
|
for (local i = 0; i < session.getCommandCount(); ++i) {
|
|
if (session.getCommandId(i) == "test.script.place-attraction" &&
|
|
session.getCommandName(i) == "Place Attraction" &&
|
|
session.getCommandCategory(i) == "Build") found = true;
|
|
}
|
|
planned <- session.planCommand("test.script.place-attraction", {
|
|
amount = 7,
|
|
placement = { x = 12.5, y = 4.0 },
|
|
tags = ["ride", "family"]
|
|
});
|
|
executed <- session.executePlan(planned.planId, {});
|
|
)"));
|
|
|
|
CHECK(vm.find("found").toBool());
|
|
ssq::Table planned(vm.find("planned"));
|
|
ssq::Table executed(vm.find("executed"));
|
|
CHECK(planned.get<bool>("accepted"));
|
|
CHECK_EQ(planned.get<std::string>("status"), std::string("applied"));
|
|
CHECK(executed.get<bool>("accepted"));
|
|
CHECK_EQ(executed.get<std::string>("transactionState"), std::string("committed"));
|
|
CHECK_EQ(executed.get<int64_t>("afterRevision"), int64_t{1});
|
|
CHECK(plannerSawScriptSource);
|
|
CHECK_EQ(executedAmount, 7);
|
|
|
|
CHECK(commands.unregisterCommand(commandId, "test.script"));
|
|
}
|
|
|
|
TEST_CASE("editor.v2.script_game_injects_command_into_the_same_session_protocol") {
|
|
Editor* editor = Editor::create();
|
|
editor->commandService().unregisterCommand(CommandId("test.script.spawn-tree"));
|
|
|
|
ssq::VM vm(1024, ssq::Libs::ALL);
|
|
eve::ModuleManager::expose(vm);
|
|
vm.run(vm.compileSource(R"(
|
|
editor <- eve.Editor();
|
|
placed <- 0;
|
|
registered <- editor.registerScriptCommand(
|
|
"test.script.spawn-tree", "Spawn Tree", "Build",
|
|
function(payload) {
|
|
if (!("count" in payload) || payload.count <= 0) return false;
|
|
placed += payload.count;
|
|
return true;
|
|
});
|
|
session <- editor.newSession();
|
|
planned <- session.planCommand("test.script.spawn-tree", { count = 3 });
|
|
executed <- session.executePlan(planned.planId, {});
|
|
)"));
|
|
|
|
CHECK(vm.find("registered").toBool());
|
|
CHECK_EQ(vm.find("placed").toInt(), 3);
|
|
ssq::Table executed(vm.find("executed"));
|
|
CHECK(executed.get<bool>("accepted"));
|
|
CHECK_EQ(executed.get<std::string>("authorityReceipt"), std::string("script:local"));
|
|
|
|
CHECK(editor->commandService().unregisterCommand(CommandId("test.script.spawn-tree"),
|
|
"script:test.script.spawn-tree"));
|
|
}
|
|
|
|
TEST_CASE("editor.v2.runtime_builder_quick_add_click_executes_command") {
|
|
const std::filesystem::path sourceRoot = std::filesystem::path(__FILE__).parent_path().parent_path();
|
|
const std::filesystem::path scriptPath = sourceRoot / "examples" / "editor-api-v2" / "main.nut";
|
|
std::ifstream input(scriptPath, std::ios::binary);
|
|
REQUIRE(input.is_open());
|
|
std::ostringstream source;
|
|
source << input.rdbuf();
|
|
|
|
const std::string harness = R"(
|
|
eve_init <- null;
|
|
eve_update <- null;
|
|
eve_render <- null;
|
|
persist <- function(key, factory) { return factory(); };
|
|
gfx <- {
|
|
rects = [],
|
|
canvasDraws = 0,
|
|
newCanvas = function(w, h) { return { width=w, height=h }; },
|
|
setCanvas = function(canvas) {},
|
|
drawCanvas = function(canvas, x, y, w, h) { canvasDraws += 1; },
|
|
setBackgroundColor = function(r, g, b, a) {},
|
|
clear = function() {},
|
|
drawSolidRect = function(x, y, w, h, r, g, b, a) {
|
|
rects.append({ x=x, y=y, w=w, h=h, r=r, g=g, b=b, a=a });
|
|
}
|
|
};
|
|
mouse <- {
|
|
isDown = function(button) { return false; },
|
|
getX = function() { return 0.0; },
|
|
getY = function() { return 0.0; }
|
|
};
|
|
ui <- {
|
|
clicks = [],
|
|
setTheme = function(name) {},
|
|
beginBuild = function() {},
|
|
beginWindow = function(title, id) {},
|
|
text = function(value, id) {},
|
|
beginRow = function(id, gap) {},
|
|
button = function(label, id) {},
|
|
end = function() {},
|
|
mountBuildAs = function(name) {},
|
|
select = function(name) {},
|
|
setHostOverlay = function(enabled) {},
|
|
setHostPos = function(x, y, z, w) {},
|
|
setHostSize = function(w, h) {},
|
|
setText = function(id, value) {},
|
|
wantCaptureMouse = function() { return false; },
|
|
beginFrameAndRender = function() {},
|
|
consumeClick = function() {
|
|
if (clicks.len() == 0) return "";
|
|
local value = clicks[0];
|
|
clicks.remove(0);
|
|
return value;
|
|
}
|
|
};
|
|
)";
|
|
|
|
ssq::VM vm(4096, ssq::Libs::ALL);
|
|
eve::ModuleManager::expose(vm);
|
|
const std::string executable = harness + source.str() + R"(
|
|
eve_init();
|
|
ui.clicks.append("editor-v2/asset-tree");
|
|
eve_update(0.016);
|
|
eve_render();
|
|
quickAddObjectCount <- editorV2.objects.len();
|
|
quickAddCredits <- editorV2.credits;
|
|
quickAddAsset <- editorV2.objects[0].asset;
|
|
quickAddCanvasDraws <- gfx.canvasDraws;
|
|
quickAddRenderedTree <- false;
|
|
foreach (rect in gfx.rects) {
|
|
if (rect.w >= 18.0 && rect.h >= 12.0 && rect.g > 0.6)
|
|
quickAddRenderedTree = true;
|
|
}
|
|
cleanupResult <- editorV2.editor.unregisterScriptCommand("park.scene.place-asset");
|
|
)";
|
|
vm.run(vm.compileSource(executable.c_str(), "examples/editor-api-v2/main.nut"));
|
|
|
|
CHECK_EQ(vm.find("quickAddObjectCount").toInt(), 1);
|
|
CHECK_EQ(vm.find("quickAddCredits").toInt(), 580);
|
|
CHECK_EQ(vm.find("quickAddAsset").toString(), std::string("park.asset.tree"));
|
|
CHECK_EQ(vm.find("quickAddCanvasDraws").toInt(), 1);
|
|
CHECK(vm.find("quickAddRenderedTree").toBool());
|
|
CHECK(vm.find("cleanupResult").toBool());
|
|
}
|