More Compilation stuff lol.
This commit is contained in:
@@ -9,21 +9,100 @@
|
|||||||
#include "SceneObject.h"
|
#include "SceneObject.h"
|
||||||
#include "ThirdParty/imgui/imgui.h"
|
#include "ThirdParty/imgui/imgui.h"
|
||||||
#include <string>
|
#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) {
|
extern "C" void Script_OnInspector(ScriptContext& ctx) {
|
||||||
static bool autoRotate = false;
|
loadSettings(ctx);
|
||||||
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";
|
|
||||||
|
|
||||||
ImGui::TextUnformatted("SampleInspector");
|
ImGui::TextUnformatted("SampleInspector");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
ImGui::Checkbox("Auto Rotate", &autoRotate);
|
if (ImGui::Checkbox("Auto Rotate", &autoRotate)) {
|
||||||
ImGui::DragFloat3("Spin Speed (deg/s)", &spinSpeed.x, 1.0f, -360.0f, 360.0f);
|
persistSettings(ctx);
|
||||||
ImGui::DragFloat3("Offset", &offset.x, 0.1f);
|
}
|
||||||
|
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));
|
ImGui::InputText("Target Name", targetName, sizeof(targetName));
|
||||||
|
persistSettings(ctx);
|
||||||
|
|
||||||
if (ctx.object) {
|
if (ctx.object) {
|
||||||
ImGui::TextDisabled("Attached to: %s (id=%d)", ctx.object->name.c_str(), ctx.object->id);
|
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;
|
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);
|
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) {
|
if (!showLauncher && projectManager.currentProject.isLoaded && rendererInitialized) {
|
||||||
glm::mat4 view = camera.getViewMatrix();
|
glm::mat4 view = camera.getViewMatrix();
|
||||||
float aspect = static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight);
|
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) {
|
void Engine::OpenProjectPath(const std::string& path) {
|
||||||
try {
|
try {
|
||||||
if (projectManager.loadProject(path)) {
|
if (projectManager.loadProject(path)) {
|
||||||
@@ -1025,6 +1048,10 @@ fs::path Engine::resolveScriptBinary(const fs::path& sourcePath) {
|
|||||||
return cmds.binaryPath;
|
return cmds.binaryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::markProjectDirty() {
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::compileScriptFile(const fs::path& scriptPath) {
|
void Engine::compileScriptFile(const fs::path& scriptPath) {
|
||||||
if (!projectManager.currentProject.isLoaded) {
|
if (!projectManager.currentProject.isLoaded) {
|
||||||
addConsoleMessage("No project is loaded", ConsoleMessageType::Warning);
|
addConsoleMessage("No project is loaded", ConsoleMessageType::Warning);
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ private:
|
|||||||
bool lastCompileSuccess = false;
|
bool lastCompileSuccess = false;
|
||||||
std::string lastCompileStatus;
|
std::string lastCompileStatus;
|
||||||
std::string lastCompileLog;
|
std::string lastCompileLog;
|
||||||
|
bool specMode = false;
|
||||||
|
bool testMode = false;
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
SceneObject* getSelectedObject();
|
SceneObject* getSelectedObject();
|
||||||
@@ -143,6 +145,7 @@ private:
|
|||||||
void renderProjectBrowserPanel();
|
void renderProjectBrowserPanel();
|
||||||
Camera makeCameraFromObject(const SceneObject& obj) const;
|
Camera makeCameraFromObject(const SceneObject& obj) const;
|
||||||
void compileScriptFile(const fs::path& scriptPath);
|
void compileScriptFile(const fs::path& scriptPath);
|
||||||
|
void updateScripts(float delta);
|
||||||
|
|
||||||
void renderFileBrowserToolbar();
|
void renderFileBrowserToolbar();
|
||||||
void renderFileBrowserBreadcrumb();
|
void renderFileBrowserBreadcrumb();
|
||||||
@@ -200,4 +203,5 @@ public:
|
|||||||
SceneObject* findObjectByName(const std::string& name);
|
SceneObject* findObjectByName(const std::string& name);
|
||||||
SceneObject* findObjectById(int id);
|
SceneObject* findObjectById(int id);
|
||||||
fs::path resolveScriptBinary(const fs::path& sourcePath);
|
fs::path resolveScriptBinary(const fs::path& sourcePath);
|
||||||
|
void markProjectDirty();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1306,6 +1306,12 @@ void Engine::renderMainMenuBar() {
|
|||||||
ImGui::EndMenu();
|
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::BeginMenu("Create")) {
|
||||||
if (ImGui::MenuItem("Cube")) addObject(ObjectType::Cube, "Cube");
|
if (ImGui::MenuItem("Cube")) addObject(ObjectType::Cube, "Cube");
|
||||||
if (ImGui::MenuItem("Sphere")) addObject(ObjectType::Sphere, "Sphere");
|
if (ImGui::MenuItem("Sphere")) addObject(ObjectType::Sphere, "Sphere");
|
||||||
|
|||||||
@@ -361,8 +361,17 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
SceneObject* currentObj = nullptr;
|
SceneObject* currentObj = nullptr;
|
||||||
|
|
||||||
while (std::getline(file, line)) {
|
while (std::getline(file, line)) {
|
||||||
line.erase(0, line.find_first_not_of(" \t\r\n"));
|
size_t first = line.find_first_not_of(" \t\r\n");
|
||||||
line.erase(line.find_last_not_of(" \t\r\n") + 1);
|
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;
|
if (line.empty() || line[0] == '#') continue;
|
||||||
|
|
||||||
@@ -450,11 +459,13 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
ScriptComponent& sc = currentObj->scripts[idx];
|
ScriptComponent& sc = currentObj->scripts[idx];
|
||||||
if (sub == "path") {
|
if (sub == "path") {
|
||||||
sc.path = value;
|
sc.path = value;
|
||||||
} else if (sub == "settings") {
|
} else if (sub == "settings" || sub == "settingCount") {
|
||||||
int cnt = std::stoi(value);
|
int cnt = std::stoi(value);
|
||||||
sc.settings.resize(std::max(0, cnt));
|
sc.settings.resize(std::max(0, cnt));
|
||||||
} else if (sub.rfind("setting", 0) == 0) {
|
} else if (sub.rfind("setting", 0) == 0) {
|
||||||
int sIdx = std::stoi(sub.substr(7));
|
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()) {
|
if (sIdx >= 0 && sIdx < (int)sc.settings.size()) {
|
||||||
size_t sep = value.find(':');
|
size_t sep = value.find(':');
|
||||||
if (sep != std::string::npos) {
|
if (sep != std::string::npos) {
|
||||||
@@ -467,6 +478,7 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (key == "lightColor") {
|
} else if (key == "lightColor") {
|
||||||
sscanf(value.c_str(), "%f,%f,%f",
|
sscanf(value.c_str(), "%f,%f,%f",
|
||||||
¤tObj->light.color.r,
|
¤tObj->light.color.r,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <regex>
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -184,6 +185,118 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
|||||||
binaryPath /= baseName + ".so";
|
binaryPath /= baseName + ".so";
|
||||||
#endif
|
#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;
|
std::ostringstream compileCmd;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
compileCmd << "cl /nologo /std:" << config.cppStandard << " /EHsc /MD /Zi /Od";
|
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) {
|
for (const auto& def : config.defines) {
|
||||||
compileCmd << " /D" << escapeDefine(def);
|
compileCmd << " /D" << escapeDefine(def);
|
||||||
}
|
}
|
||||||
compileCmd << " /c \"" << scriptAbs.string() << "\" /Fo\"" << objectPath.string() << "\"";
|
compileCmd << " /c \"" << sourceToCompile.string() << "\" /Fo\"" << objectPath.string() << "\"";
|
||||||
#else
|
#else
|
||||||
compileCmd << "g++ -std=" << config.cppStandard << " -fPIC -O0 -g";
|
compileCmd << "g++ -std=" << config.cppStandard << " -fPIC -O0 -g";
|
||||||
for (const auto& inc : config.includeDirs) {
|
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) {
|
for (const auto& def : config.defines) {
|
||||||
compileCmd << formatDefine(def);
|
compileCmd << formatDefine(def);
|
||||||
}
|
}
|
||||||
compileCmd << " -c \"" << scriptAbs.string() << "\" -o \"" << objectPath.string() << "\"";
|
compileCmd << " -c \"" << sourceToCompile.string() << "\" -o \"" << objectPath.string() << "\"";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::ostringstream linkCmd;
|
std::ostringstream linkCmd;
|
||||||
@@ -233,6 +346,8 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat
|
|||||||
outCommands.link = linkCmd.str();
|
outCommands.link = linkCmd.str();
|
||||||
outCommands.objectPath = objectPath;
|
outCommands.objectPath = objectPath;
|
||||||
outCommands.binaryPath = binaryPath;
|
outCommands.binaryPath = binaryPath;
|
||||||
|
outCommands.wrapperPath = wrapperPath;
|
||||||
|
outCommands.usedWrapper = useWrapper;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ struct ScriptBuildCommands {
|
|||||||
std::string link;
|
std::string link;
|
||||||
fs::path objectPath;
|
fs::path objectPath;
|
||||||
fs::path binaryPath;
|
fs::path binaryPath;
|
||||||
|
fs::path wrapperPath;
|
||||||
|
bool usedWrapper = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScriptCompileOutput {
|
struct ScriptCompileOutput {
|
||||||
|
|||||||
@@ -30,13 +30,25 @@ void ScriptContext::SetScale(const glm::vec3& scl) {
|
|||||||
if (object) object->scale = scl;
|
if (object) object->scale = scl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptContext::MarkDirty() {
|
||||||
|
if (engine) {
|
||||||
|
engine->markProjectDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ScriptRuntime::InspectorFn ScriptRuntime::getInspector(const fs::path& binaryPath) {
|
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();
|
lastError.clear();
|
||||||
if (binaryPath.empty()) return nullptr;
|
if (binaryPath.empty()) return nullptr;
|
||||||
auto key = binaryPath.string();
|
auto key = binaryPath.string();
|
||||||
auto it = loaded.find(key);
|
auto it = loaded.find(key);
|
||||||
if (it != loaded.end()) {
|
if (it != loaded.end()) {
|
||||||
if (it->second.inspector) return it->second.inspector;
|
return &it->second;
|
||||||
// Previously loaded but missing inspector; try reloading.
|
// Previously loaded but missing inspector; try reloading.
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
if (it->second.handle) FreeLibrary(static_cast<HMODULE>(it->second.handle));
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
mod.inspector = reinterpret_cast<InspectorFn>(GetProcAddress(static_cast<HMODULE>(mod.handle), "Script_OnInspector"));
|
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
|
#else
|
||||||
mod.handle = dlopen(binaryPath.string().c_str(), RTLD_NOW);
|
mod.handle = dlopen(binaryPath.string().c_str(), RTLD_NOW);
|
||||||
if (!mod.handle) {
|
if (!mod.handle) {
|
||||||
@@ -62,26 +79,60 @@ ScriptRuntime::InspectorFn ScriptRuntime::getInspector(const fs::path& binaryPat
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
mod.inspector = reinterpret_cast<InspectorFn>(dlsym(mod.handle, "Script_OnInspector"));
|
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)
|
#if !defined(_WIN32)
|
||||||
{
|
{
|
||||||
const char* err = dlerror();
|
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
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!mod.inspector) {
|
if (!mod.inspector && !mod.begin && !mod.spec && !mod.testEditor
|
||||||
|
&& !mod.update && !mod.tickUpdate) {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
FreeLibrary(static_cast<HMODULE>(mod.handle));
|
FreeLibrary(static_cast<HMODULE>(mod.handle));
|
||||||
#else
|
#else
|
||||||
dlclose(mod.handle);
|
dlclose(mod.handle);
|
||||||
#endif
|
#endif
|
||||||
if (lastError.empty()) lastError = "Script_OnInspector not found";
|
if (lastError.empty()) lastError = "No script exports found";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded[key] = mod;
|
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() {
|
void ScriptRuntime::unloadAll() {
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "SceneObject.h"
|
#include "SceneObject.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
class Engine;
|
class Engine;
|
||||||
|
|
||||||
struct ScriptContext {
|
struct ScriptContext {
|
||||||
Engine* engine = nullptr;
|
Engine* engine = nullptr;
|
||||||
SceneObject* object = nullptr;
|
SceneObject* object = nullptr;
|
||||||
|
ScriptComponent* script = nullptr;
|
||||||
|
|
||||||
// Convenience helpers for scripts
|
// Convenience helpers for scripts
|
||||||
SceneObject* FindObjectByName(const std::string& name);
|
SceneObject* FindObjectByName(const std::string& name);
|
||||||
@@ -15,13 +17,21 @@ struct ScriptContext {
|
|||||||
void SetPosition(const glm::vec3& pos);
|
void SetPosition(const glm::vec3& pos);
|
||||||
void SetRotation(const glm::vec3& rot);
|
void SetRotation(const glm::vec3& rot);
|
||||||
void SetScale(const glm::vec3& scl);
|
void SetScale(const glm::vec3& scl);
|
||||||
|
void MarkDirty();
|
||||||
};
|
};
|
||||||
|
|
||||||
class ScriptRuntime {
|
class ScriptRuntime {
|
||||||
public:
|
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&);
|
using InspectorFn = void(*)(ScriptContext&);
|
||||||
|
|
||||||
InspectorFn getInspector(const fs::path& binaryPath);
|
InspectorFn getInspector(const fs::path& binaryPath);
|
||||||
|
void tickModule(const fs::path& binaryPath, ScriptContext& ctx, float deltaTime,
|
||||||
|
bool runSpec, bool runTest);
|
||||||
void unloadAll();
|
void unloadAll();
|
||||||
const std::string& getLastError() const { return lastError; }
|
const std::string& getLastError() const { return lastError; }
|
||||||
|
|
||||||
@@ -29,7 +39,14 @@ private:
|
|||||||
struct Module {
|
struct Module {
|
||||||
void* handle = nullptr;
|
void* handle = nullptr;
|
||||||
InspectorFn inspector = 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::unordered_map<std::string, Module> loaded;
|
||||||
std::string lastError;
|
std::string lastError;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user