More Compilation stuff lol.
This commit is contained in:
@@ -9,21 +9,100 @@
|
||||
#include "SceneObject.h"
|
||||
#include "ThirdParty/imgui/imgui.h"
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
bool autoRotate = false;
|
||||
glm::vec3 spinSpeed = glm::vec3(0.0f, 45.0f, 0.0f);
|
||||
glm::vec3 offset = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
char targetName[128] = "MyTarget";
|
||||
int settingsLoadedForId = -1;
|
||||
ScriptComponent* settingsLoadedForScript = nullptr;
|
||||
|
||||
void setSetting(ScriptContext& ctx, const std::string& key, const std::string& value) {
|
||||
if (!ctx.script) return;
|
||||
auto it = std::find_if(ctx.script->settings.begin(), ctx.script->settings.end(),
|
||||
[&](const ScriptSetting& s) { return s.key == key; });
|
||||
if (it != ctx.script->settings.end()) {
|
||||
it->value = value;
|
||||
} else {
|
||||
ctx.script->settings.push_back({key, value});
|
||||
}
|
||||
}
|
||||
|
||||
std::string getSetting(const ScriptContext& ctx, const std::string& key, const std::string& fallback = "") {
|
||||
if (!ctx.script) return fallback;
|
||||
auto it = std::find_if(ctx.script->settings.begin(), ctx.script->settings.end(),
|
||||
[&](const ScriptSetting& s) { return s.key == key; });
|
||||
return (it != ctx.script->settings.end()) ? it->value : fallback;
|
||||
}
|
||||
|
||||
void loadSettings(ScriptContext& ctx) {
|
||||
if (!ctx.script || !ctx.object) return;
|
||||
if (settingsLoadedForId == ctx.object->id && settingsLoadedForScript == ctx.script) return;
|
||||
settingsLoadedForId = ctx.object->id;
|
||||
settingsLoadedForScript = ctx.script;
|
||||
|
||||
auto parseBool = [](const std::string& v, bool def) {
|
||||
if (v == "1" || v == "true") return true;
|
||||
if (v == "0" || v == "false") return false;
|
||||
return def;
|
||||
};
|
||||
|
||||
auto parseVec3 = [](const std::string& v, const glm::vec3& def) {
|
||||
glm::vec3 out = def;
|
||||
std::stringstream ss(v);
|
||||
std::string part;
|
||||
for (int i = 0; i < 3 && std::getline(ss, part, ','); ++i) {
|
||||
try { out[i] = std::stof(part); } catch (...) {}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
autoRotate = parseBool(getSetting(ctx, "autoRotate", autoRotate ? "1" : "0"), autoRotate);
|
||||
spinSpeed = parseVec3(getSetting(ctx, "spinSpeed", ""), spinSpeed);
|
||||
offset = parseVec3(getSetting(ctx, "offset", ""), offset);
|
||||
std::string tgt = getSetting(ctx, "targetName", targetName);
|
||||
if (!tgt.empty()) {
|
||||
std::snprintf(targetName, sizeof(targetName), "%s", tgt.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void persistSettings(ScriptContext& ctx) {
|
||||
setSetting(ctx, "autoRotate", autoRotate ? "1" : "0");
|
||||
setSetting(ctx, "spinSpeed",
|
||||
std::to_string(spinSpeed.x) + "," + std::to_string(spinSpeed.y) + "," + std::to_string(spinSpeed.z));
|
||||
setSetting(ctx, "offset",
|
||||
std::to_string(offset.x) + "," + std::to_string(offset.y) + "," + std::to_string(offset.z));
|
||||
setSetting(ctx, "targetName", targetName);
|
||||
ctx.MarkDirty();
|
||||
}
|
||||
|
||||
void applyAutoRotate(ScriptContext& ctx, float deltaTime) {
|
||||
if (!autoRotate || !ctx.object) return;
|
||||
ctx.SetRotation(ctx.object->rotation + spinSpeed * deltaTime);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
||||
static bool autoRotate = false;
|
||||
static glm::vec3 spinSpeed = glm::vec3(0.0f, 45.0f, 0.0f);
|
||||
static glm::vec3 offset = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
static char targetName[128] = "MyTarget";
|
||||
loadSettings(ctx);
|
||||
|
||||
ImGui::TextUnformatted("SampleInspector");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Checkbox("Auto Rotate", &autoRotate);
|
||||
ImGui::DragFloat3("Spin Speed (deg/s)", &spinSpeed.x, 1.0f, -360.0f, 360.0f);
|
||||
ImGui::DragFloat3("Offset", &offset.x, 0.1f);
|
||||
if (ImGui::Checkbox("Auto Rotate", &autoRotate)) {
|
||||
persistSettings(ctx);
|
||||
}
|
||||
if (ImGui::DragFloat3("Spin Speed (deg/s)", &spinSpeed.x, 1.0f, -360.0f, 360.0f)) {
|
||||
persistSettings(ctx);
|
||||
}
|
||||
if (ImGui::DragFloat3("Offset", &offset.x, 0.1f)) {
|
||||
persistSettings(ctx);
|
||||
}
|
||||
|
||||
ImGui::InputText("Target Name", targetName, sizeof(targetName));
|
||||
persistSettings(ctx);
|
||||
|
||||
if (ctx.object) {
|
||||
ImGui::TextDisabled("Attached to: %s (id=%d)", ctx.object->name.c_str(), ctx.object->id);
|
||||
@@ -38,8 +117,24 @@ extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
||||
target->position += offset;
|
||||
}
|
||||
}
|
||||
|
||||
if (autoRotate && ctx.object) {
|
||||
ctx.SetRotation(ctx.object->rotation + spinSpeed * (1.0f / 60.0f));
|
||||
}
|
||||
}
|
||||
|
||||
// New lifecycle hooks supported by the compiler wrapper. These are optional stubs demonstrating usage.
|
||||
void Begin(ScriptContext& ctx, float /*deltaTime*/) {
|
||||
// Initialize per-script state here.
|
||||
loadSettings(ctx);
|
||||
}
|
||||
|
||||
void Spec(ScriptContext& ctx, float deltaTime) {
|
||||
// Special/speculative mode logic could go here.
|
||||
applyAutoRotate(ctx, deltaTime);
|
||||
}
|
||||
|
||||
void TestEditor(ScriptContext& ctx, float deltaTime) {
|
||||
// Editor-time behavior entry point.
|
||||
applyAutoRotate(ctx, deltaTime);
|
||||
}
|
||||
|
||||
void TickUpdate(ScriptContext& ctx, float deltaTime) {
|
||||
applyAutoRotate(ctx, deltaTime);
|
||||
}
|
||||
|
||||
@@ -303,6 +303,11 @@ void Engine::run() {
|
||||
camera.processKeyboard(deltaTime, editorWindow);
|
||||
}
|
||||
|
||||
// Run script tick/update even when the object is not selected.
|
||||
if (projectManager.currentProject.isLoaded) {
|
||||
updateScripts(deltaTime);
|
||||
}
|
||||
|
||||
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
||||
glm::mat4 view = camera.getViewMatrix();
|
||||
float aspect = static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight);
|
||||
@@ -691,6 +696,24 @@ void Engine::handleKeyboardShortcuts() {
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::updateScripts(float delta) {
|
||||
if (sceneObjects.empty()) return;
|
||||
|
||||
for (auto& obj : sceneObjects) {
|
||||
for (auto& sc : obj.scripts) {
|
||||
if (sc.path.empty()) continue;
|
||||
fs::path binary = resolveScriptBinary(sc.path);
|
||||
if (binary.empty() || !fs::exists(binary)) continue;
|
||||
ScriptContext ctx;
|
||||
ctx.engine = this;
|
||||
ctx.object = &obj;
|
||||
ctx.script = ≻
|
||||
|
||||
scriptRuntime.tickModule(binary, ctx, delta, specMode, testMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::OpenProjectPath(const std::string& path) {
|
||||
try {
|
||||
if (projectManager.loadProject(path)) {
|
||||
@@ -1025,6 +1048,10 @@ fs::path Engine::resolveScriptBinary(const fs::path& sourcePath) {
|
||||
return cmds.binaryPath;
|
||||
}
|
||||
|
||||
void Engine::markProjectDirty() {
|
||||
projectManager.currentProject.hasUnsavedChanges = true;
|
||||
}
|
||||
|
||||
void Engine::compileScriptFile(const fs::path& scriptPath) {
|
||||
if (!projectManager.currentProject.isLoaded) {
|
||||
addConsoleMessage("No project is loaded", ConsoleMessageType::Warning);
|
||||
|
||||
@@ -109,6 +109,8 @@ private:
|
||||
bool lastCompileSuccess = false;
|
||||
std::string lastCompileStatus;
|
||||
std::string lastCompileLog;
|
||||
bool specMode = false;
|
||||
bool testMode = false;
|
||||
|
||||
// Private methods
|
||||
SceneObject* getSelectedObject();
|
||||
@@ -143,6 +145,7 @@ private:
|
||||
void renderProjectBrowserPanel();
|
||||
Camera makeCameraFromObject(const SceneObject& obj) const;
|
||||
void compileScriptFile(const fs::path& scriptPath);
|
||||
void updateScripts(float delta);
|
||||
|
||||
void renderFileBrowserToolbar();
|
||||
void renderFileBrowserBreadcrumb();
|
||||
@@ -200,4 +203,5 @@ public:
|
||||
SceneObject* findObjectByName(const std::string& name);
|
||||
SceneObject* findObjectById(int id);
|
||||
fs::path resolveScriptBinary(const fs::path& sourcePath);
|
||||
void markProjectDirty();
|
||||
};
|
||||
|
||||
@@ -1306,6 +1306,12 @@ void Engine::renderMainMenuBar() {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Scripts")) {
|
||||
ImGui::MenuItem("Spec Mode (run Script_Spec)", nullptr, &specMode);
|
||||
ImGui::MenuItem("Test Mode (run Script_TestEditor)", nullptr, &testMode);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Create")) {
|
||||
if (ImGui::MenuItem("Cube")) addObject(ObjectType::Cube, "Cube");
|
||||
if (ImGui::MenuItem("Sphere")) addObject(ObjectType::Sphere, "Sphere");
|
||||
|
||||
@@ -361,8 +361,17 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
SceneObject* currentObj = nullptr;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
line.erase(0, line.find_first_not_of(" \t\r\n"));
|
||||
line.erase(line.find_last_not_of(" \t\r\n") + 1);
|
||||
size_t first = line.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
line.erase(0, first);
|
||||
size_t last = line.find_last_not_of(" \t\r\n");
|
||||
if (last != std::string::npos) {
|
||||
line.erase(last + 1);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
@@ -450,18 +459,21 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
||||
ScriptComponent& sc = currentObj->scripts[idx];
|
||||
if (sub == "path") {
|
||||
sc.path = value;
|
||||
} else if (sub == "settings") {
|
||||
} else if (sub == "settings" || sub == "settingCount") {
|
||||
int cnt = std::stoi(value);
|
||||
sc.settings.resize(std::max(0, cnt));
|
||||
} else if (sub.rfind("setting", 0) == 0) {
|
||||
int sIdx = std::stoi(sub.substr(7));
|
||||
if (sIdx >= 0 && sIdx < (int)sc.settings.size()) {
|
||||
size_t sep = value.find(':');
|
||||
if (sep != std::string::npos) {
|
||||
sc.settings[sIdx].key = value.substr(0, sep);
|
||||
sc.settings[sIdx].value = value.substr(sep + 1);
|
||||
} else {
|
||||
sc.settings[sIdx].value = value;
|
||||
std::string idxStr = sub.substr(7);
|
||||
if (!idxStr.empty() && std::all_of(idxStr.begin(), idxStr.end(), ::isdigit)) {
|
||||
int sIdx = std::stoi(idxStr);
|
||||
if (sIdx >= 0 && sIdx < (int)sc.settings.size()) {
|
||||
size_t sep = value.find(':');
|
||||
if (sep != std::string::npos) {
|
||||
sc.settings[sIdx].key = value.substr(0, sep);
|
||||
sc.settings[sIdx].value = value.substr(sep + 1);
|
||||
} else {
|
||||
sc.settings[sIdx].value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
@@ -184,6 +185,118 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
binaryPath /= baseName + ".so";
|
||||
#endif
|
||||
|
||||
// Build a lightweight wrapper that exposes expected entry points with C linkage and optional deltaTime.
|
||||
auto readFileToString = [](const fs::path& path, std::string& contents) -> bool {
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) return false;
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
contents = ss.str();
|
||||
return true;
|
||||
};
|
||||
|
||||
struct FunctionSpec {
|
||||
bool present = false;
|
||||
bool takesDelta = false;
|
||||
bool takesContext = false;
|
||||
};
|
||||
|
||||
auto detectFunction = [](const std::string& source, const std::string& name) -> FunctionSpec {
|
||||
FunctionSpec spec;
|
||||
try {
|
||||
std::regex ctxDeltaPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*ScriptContext\\s*[&*][^,\\)]*,[^\\)]*(float|double)[^\\)]*\\)");
|
||||
std::regex ctxOnlyPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*ScriptContext\\s*[&*][^\\)]*\\)");
|
||||
std::regex deltaPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*(float|double)[^\\)]*\\)");
|
||||
std::regex basicPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*\\)");
|
||||
|
||||
if (std::regex_search(source, ctxDeltaPattern)) {
|
||||
spec.present = true;
|
||||
spec.takesDelta = true;
|
||||
spec.takesContext = true;
|
||||
return spec;
|
||||
}
|
||||
if (std::regex_search(source, ctxOnlyPattern)) {
|
||||
spec.present = true;
|
||||
spec.takesContext = true;
|
||||
return spec;
|
||||
}
|
||||
if (std::regex_search(source, deltaPattern)) {
|
||||
spec.present = true;
|
||||
spec.takesDelta = true;
|
||||
return spec;
|
||||
}
|
||||
if (std::regex_search(source, basicPattern)) {
|
||||
spec.present = true;
|
||||
}
|
||||
} catch (...) {
|
||||
// If regex throws for any reason, fall through and treat as not present.
|
||||
}
|
||||
return spec;
|
||||
};
|
||||
|
||||
std::string scriptSource;
|
||||
if (!readFileToString(scriptAbs, scriptSource)) {
|
||||
error = "Unable to read script file: " + scriptAbs.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionSpec beginSpec = detectFunction(scriptSource, "Begin");
|
||||
FunctionSpec specSpec = detectFunction(scriptSource, "Spec");
|
||||
FunctionSpec testEditorSpec = detectFunction(scriptSource, "TestEditor");
|
||||
FunctionSpec updateSpec = detectFunction(scriptSource, "Update");
|
||||
FunctionSpec tickUpdateSpec = detectFunction(scriptSource, "TickUpdate");
|
||||
|
||||
fs::path wrapperPath;
|
||||
bool useWrapper = beginSpec.present || specSpec.present || testEditorSpec.present
|
||||
|| updateSpec.present || tickUpdateSpec.present;
|
||||
fs::path sourceToCompile = scriptAbs;
|
||||
|
||||
if (useWrapper) {
|
||||
wrapperPath = config.outDir / relativeParent / (baseName + ".wrap.cpp");
|
||||
std::error_code createErr;
|
||||
fs::create_directories(wrapperPath.parent_path(), createErr);
|
||||
|
||||
std::ofstream wrapper(wrapperPath);
|
||||
if (!wrapper.is_open()) {
|
||||
error = "Unable to write wrapper file: " + wrapperPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string includePath = scriptAbs.lexically_normal().generic_string();
|
||||
wrapper << "#include \"ScriptRuntime.h\"\n";
|
||||
wrapper << "#include \"" << includePath << "\"\n\n";
|
||||
wrapper << "extern \"C\" {\n";
|
||||
|
||||
auto emitWrapper = [&](const char* exportedName, const char* implName,
|
||||
const FunctionSpec& spec) {
|
||||
if (!spec.present) return;
|
||||
wrapper << "void " << exportedName << "(ScriptContext& ctx, float deltaTime) {\n";
|
||||
if (spec.takesContext && spec.takesDelta) {
|
||||
wrapper << " " << implName << "(ctx, deltaTime);\n";
|
||||
} else if (spec.takesContext) {
|
||||
wrapper << " (void)deltaTime;\n";
|
||||
wrapper << " " << implName << "(ctx);\n";
|
||||
} else if (spec.takesDelta) {
|
||||
wrapper << " (void)ctx;\n";
|
||||
wrapper << " " << implName << "(deltaTime);\n";
|
||||
} else {
|
||||
wrapper << " (void)ctx;\n";
|
||||
wrapper << " (void)deltaTime;\n";
|
||||
wrapper << " " << implName << "();\n";
|
||||
}
|
||||
wrapper << "}\n\n";
|
||||
};
|
||||
|
||||
emitWrapper("Script_Begin", "Begin", beginSpec);
|
||||
emitWrapper("Script_Spec", "Spec", specSpec);
|
||||
emitWrapper("Script_TestEditor", "TestEditor", testEditorSpec);
|
||||
emitWrapper("Script_Update", "Update", updateSpec);
|
||||
emitWrapper("Script_TickUpdate", "TickUpdate", tickUpdateSpec);
|
||||
|
||||
wrapper << "}\n";
|
||||
sourceToCompile = wrapperPath;
|
||||
}
|
||||
|
||||
std::ostringstream compileCmd;
|
||||
#ifdef _WIN32
|
||||
compileCmd << "cl /nologo /std:" << config.cppStandard << " /EHsc /MD /Zi /Od";
|
||||
@@ -193,7 +306,7 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
for (const auto& def : config.defines) {
|
||||
compileCmd << " /D" << escapeDefine(def);
|
||||
}
|
||||
compileCmd << " /c \"" << scriptAbs.string() << "\" /Fo\"" << objectPath.string() << "\"";
|
||||
compileCmd << " /c \"" << sourceToCompile.string() << "\" /Fo\"" << objectPath.string() << "\"";
|
||||
#else
|
||||
compileCmd << "g++ -std=" << config.cppStandard << " -fPIC -O0 -g";
|
||||
for (const auto& inc : config.includeDirs) {
|
||||
@@ -212,7 +325,7 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
for (const auto& def : config.defines) {
|
||||
compileCmd << formatDefine(def);
|
||||
}
|
||||
compileCmd << " -c \"" << scriptAbs.string() << "\" -o \"" << objectPath.string() << "\"";
|
||||
compileCmd << " -c \"" << sourceToCompile.string() << "\" -o \"" << objectPath.string() << "\"";
|
||||
#endif
|
||||
|
||||
std::ostringstream linkCmd;
|
||||
@@ -233,6 +346,8 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
||||
outCommands.link = linkCmd.str();
|
||||
outCommands.objectPath = objectPath;
|
||||
outCommands.binaryPath = binaryPath;
|
||||
outCommands.wrapperPath = wrapperPath;
|
||||
outCommands.usedWrapper = useWrapper;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ struct ScriptBuildCommands {
|
||||
std::string link;
|
||||
fs::path objectPath;
|
||||
fs::path binaryPath;
|
||||
fs::path wrapperPath;
|
||||
bool usedWrapper = false;
|
||||
};
|
||||
|
||||
struct ScriptCompileOutput {
|
||||
|
||||
@@ -30,13 +30,25 @@ void ScriptContext::SetScale(const glm::vec3& scl) {
|
||||
if (object) object->scale = scl;
|
||||
}
|
||||
|
||||
void ScriptContext::MarkDirty() {
|
||||
if (engine) {
|
||||
engine->markProjectDirty();
|
||||
}
|
||||
}
|
||||
|
||||
ScriptRuntime::InspectorFn ScriptRuntime::getInspector(const fs::path& binaryPath) {
|
||||
lastError.clear();
|
||||
Module* mod = getModule(binaryPath);
|
||||
return mod ? mod->inspector : nullptr;
|
||||
}
|
||||
|
||||
ScriptRuntime::Module* ScriptRuntime::getModule(const fs::path& binaryPath) {
|
||||
lastError.clear();
|
||||
if (binaryPath.empty()) return nullptr;
|
||||
auto key = binaryPath.string();
|
||||
auto it = loaded.find(key);
|
||||
if (it != loaded.end()) {
|
||||
if (it->second.inspector) return it->second.inspector;
|
||||
return &it->second;
|
||||
// Previously loaded but missing inspector; try reloading.
|
||||
#if defined(_WIN32)
|
||||
if (it->second.handle) FreeLibrary(static_cast<HMODULE>(it->second.handle));
|
||||
@@ -54,6 +66,11 @@ ScriptRuntime::InspectorFn ScriptRuntime::getInspector(const fs::path& binaryPat
|
||||
return nullptr;
|
||||
}
|
||||
mod.inspector = reinterpret_cast<InspectorFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_OnInspector"));
|
||||
mod.begin = reinterpret_cast<BeginFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_Begin"));
|
||||
mod.spec = reinterpret_cast<SpecFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_Spec"));
|
||||
mod.testEditor = reinterpret_cast<TestEditorFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_TestEditor"));
|
||||
mod.update = reinterpret_cast<UpdateFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_Update"));
|
||||
mod.tickUpdate = reinterpret_cast<TickUpdateFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_TickUpdate"));
|
||||
#else
|
||||
mod.handle = dlopen(binaryPath.string().c_str(), RTLD_NOW);
|
||||
if (!mod.handle) {
|
||||
@@ -62,26 +79,60 @@ ScriptRuntime::InspectorFn ScriptRuntime::getInspector(const fs::path& binaryPat
|
||||
return nullptr;
|
||||
}
|
||||
mod.inspector = reinterpret_cast<InspectorFn>(dlsym(mod.handle, "Script_OnInspector"));
|
||||
mod.begin = reinterpret_cast<BeginFn>(dlsym(mod.handle, "Script_Begin"));
|
||||
mod.spec = reinterpret_cast<SpecFn>(dlsym(mod.handle, "Script_Spec"));
|
||||
mod.testEditor = reinterpret_cast<TestEditorFn>(dlsym(mod.handle, "Script_TestEditor"));
|
||||
mod.update = reinterpret_cast<UpdateFn>(dlsym(mod.handle, "Script_Update"));
|
||||
mod.tickUpdate = reinterpret_cast<TickUpdateFn>(dlsym(mod.handle, "Script_TickUpdate"));
|
||||
#if !defined(_WIN32)
|
||||
{
|
||||
const char* err = dlerror();
|
||||
if (err && !mod.inspector) lastError = err;
|
||||
if (err && !mod.inspector && !mod.begin && !mod.spec && !mod.testEditor
|
||||
&& !mod.update && !mod.tickUpdate) {
|
||||
lastError = err;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (!mod.inspector) {
|
||||
if (!mod.inspector && !mod.begin && !mod.spec && !mod.testEditor
|
||||
&& !mod.update && !mod.tickUpdate) {
|
||||
#if defined(_WIN32)
|
||||
FreeLibrary(static_cast<HMODULE>(mod.handle));
|
||||
#else
|
||||
dlclose(mod.handle);
|
||||
#endif
|
||||
if (lastError.empty()) lastError = "Script_OnInspector not found";
|
||||
if (lastError.empty()) lastError = "No script exports found";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
loaded[key] = mod;
|
||||
return mod.inspector;
|
||||
return &loaded[key];
|
||||
}
|
||||
|
||||
void ScriptRuntime::tickModule(const fs::path& binaryPath, ScriptContext& ctx, float deltaTime,
|
||||
bool runSpec, bool runTest) {
|
||||
Module* mod = getModule(binaryPath);
|
||||
if (!mod) return;
|
||||
|
||||
int objId = ctx.object ? ctx.object->id : -1;
|
||||
if (objId >= 0 && mod->begin && mod->beginCalledObjects.find(objId) == mod->beginCalledObjects.end()) {
|
||||
mod->begin(ctx, deltaTime);
|
||||
mod->beginCalledObjects.insert(objId);
|
||||
}
|
||||
|
||||
if (mod->tickUpdate) {
|
||||
mod->tickUpdate(ctx, deltaTime);
|
||||
} else if (mod->update) {
|
||||
mod->update(ctx, deltaTime);
|
||||
}
|
||||
|
||||
if (runSpec && mod->spec) {
|
||||
mod->spec(ctx, deltaTime);
|
||||
}
|
||||
if (runTest && mod->testEditor) {
|
||||
mod->testEditor(ctx, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptRuntime::unloadAll() {
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
#include "Common.h"
|
||||
#include "SceneObject.h"
|
||||
#include <unordered_set>
|
||||
|
||||
class Engine;
|
||||
|
||||
struct ScriptContext {
|
||||
Engine* engine = nullptr;
|
||||
SceneObject* object = nullptr;
|
||||
ScriptComponent* script = nullptr;
|
||||
|
||||
// Convenience helpers for scripts
|
||||
SceneObject* FindObjectByName(const std::string& name);
|
||||
@@ -15,13 +17,21 @@ struct ScriptContext {
|
||||
void SetPosition(const glm::vec3& pos);
|
||||
void SetRotation(const glm::vec3& rot);
|
||||
void SetScale(const glm::vec3& scl);
|
||||
void MarkDirty();
|
||||
};
|
||||
|
||||
class ScriptRuntime {
|
||||
public:
|
||||
using BeginFn = void(*)(ScriptContext&, float);
|
||||
using SpecFn = void(*)(ScriptContext&, float);
|
||||
using TestEditorFn = void(*)(ScriptContext&, float);
|
||||
using UpdateFn = void(*)(ScriptContext&, float);
|
||||
using TickUpdateFn = void(*)(ScriptContext&, float);
|
||||
using InspectorFn = void(*)(ScriptContext&);
|
||||
|
||||
InspectorFn getInspector(const fs::path& binaryPath);
|
||||
void tickModule(const fs::path& binaryPath, ScriptContext& ctx, float deltaTime,
|
||||
bool runSpec, bool runTest);
|
||||
void unloadAll();
|
||||
const std::string& getLastError() const { return lastError; }
|
||||
|
||||
@@ -29,7 +39,14 @@ private:
|
||||
struct Module {
|
||||
void* handle = nullptr;
|
||||
InspectorFn inspector = nullptr;
|
||||
BeginFn begin = nullptr;
|
||||
SpecFn spec = nullptr;
|
||||
TestEditorFn testEditor = nullptr;
|
||||
UpdateFn update = nullptr;
|
||||
TickUpdateFn tickUpdate = nullptr;
|
||||
std::unordered_set<int> beginCalledObjects;
|
||||
};
|
||||
Module* getModule(const fs::path& binaryPath);
|
||||
std::unordered_map<std::string, Module> loaded;
|
||||
std::string lastError;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user