Jump to content

Josh

Staff
  • Posts

    23,321
  • Joined

  • Last visited

Everything posted by Josh

  1. My current implementation, working in Lua with indexes and keys. This is very very touchy: #include "UltraEngine.h" using namespace UltraEngine; enum JsonType { JSON_NULL, JSON_INTEGER, JSON_FLOAT, JSON_BOOLEAN, JSON_STRING, JSON_OBJECT, JSON_ARRAY }; class Json : public std::map<std::string, Json> { typedef std::map<std::string, Json> JsonObject; std::vector<Json> v; double f; int64_t i; bool b; std::string s; JsonType t; public: Json() { f = 0; i = 0; b = false; t = JSON_NULL; } Json::iterator begin() { return JsonObject::begin(); } Json::iterator end() { return JsonObject::end(); } //Json::iterator erase(Json::iterator it) //{ // if (t != JSON_OBJECT) RuntimeError("JSON value is not an object"); // return JsonObject::erase(it); //} Json::iterator find(const std::string& s) { return JsonObject::find(s); } std::pair<Json::iterator, bool> insert(std::pair<std::string, Json> pair) { return JsonObject::insert(pair); } static Json Object() { Json j; j.t = JSON_OBJECT; return j; } static Json Array() { Json j; j.t = JSON_ARRAY; return j; } JsonType GetType() const { return t; } operator bool() const { if (t == JSON_BOOLEAN) return b; return false; } operator int() const { if (t == JSON_INTEGER) return i; if (t == JSON_FLOAT) return f; if (t == JSON_BOOLEAN) return b; return 0; } operator int64_t() const { if (t == JSON_INTEGER) return i; if (t == JSON_FLOAT) return f; if (t == JSON_BOOLEAN) return b; return 0; } operator float() const { if (t == JSON_FLOAT) return f; if (t == JSON_INTEGER) return i; if (t == JSON_BOOLEAN) return b; return 0.0f; } operator double() const { if (t == JSON_FLOAT) return f; if (t == JSON_INTEGER) return i; if (t == JSON_BOOLEAN) return b; return 0.0f; } operator std::string() const { if (t == JSON_STRING) return s; if (t == JSON_FLOAT) return String(f); if (t == JSON_INTEGER) return String(i); if (t == JSON_BOOLEAN) return String(b); return ""; } void clear() { i = 0; f = 0; b = false; s.clear(); JsonObject::clear(); v.clear(); } Json(const int i_) { clear(); i = i_; t = JSON_INTEGER; } Json(const int64_t i_) { clear(); i = i_; t = JSON_INTEGER; } Json(const bool b_) { clear(); b = b_; t = JSON_BOOLEAN; } Json(const float f_) { clear(); f = f_; t = JSON_FLOAT; } Json(const double f_) { clear(); f = f_; t = JSON_FLOAT; } Json(const std::string& s_) { clear(); s = s_; t = JSON_STRING; } Json(const JsonObject& j3) { clear(); t = JSON_OBJECT; for (const auto& pair : j3) { JsonObject::insert(pair); } //JsonObject::insert(JsonObject::end(), j3.begin(), j3.end()); } Json& operator[](const char* c) { return (*this)[std::string(c)]; } Json& operator[](const std::string& key) { if (t != JSON_OBJECT) RuntimeError("JSON value is not an object"); auto it = find(key); if (it == end()) { auto pair = std::pair<std::string, Json>(key, {}); JsonObject::insert(pair); it = JsonObject::find(key); Assert(it != JsonObject::end()); } return it->second; } Json& operator[](const size_t index) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); if (index >= size()) RuntimeError("Index out of bounds"); return v[index]; } size_t size() { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); return v.size(); } void push_back(const Json& j3) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); v.push_back(j3); } void resize(const size_t sz) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); v.resize(sz); } void reserve(const size_t sz) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); v.reserve(sz); } size_t capacity() { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); return v.capacity(); } }; /*namespace sol { template <> struct is_container<Json> : std::true_type {}; }*/ /*namespace sol { template <> struct is_container<Json> : std::true_type {}; template <> struct usertype_container<Json> { ... // see below for implemetation details }; }*/ int main(int argc, const char* argv[]) { auto L = GetLuaState(); L->new_usertype<Json>("MYJSON", sol::meta_function::to_string, [](const Json& v) { std::string s = v; return s; }, sol::meta_function::index, sol::overload( [](Json& v, std::string key) { auto L = GetLuaState()->lua_state(); auto val = v[key]; switch (val.GetType()) { case JSON_INTEGER: return sol::make_object(L, int64_t(val)); case JSON_FLOAT: return sol::make_object(L, double(val)); case JSON_BOOLEAN: return sol::make_object(L, bool(val)); case JSON_STRING: return sol::make_object(L, std::string(val)); case JSON_ARRAY: case JSON_OBJECT: return sol::make_object(L, val); } }, [](Json& v, int64_t index) { auto L = GetLuaState()->lua_state(); if (index < 0 or index >= v.size()) sol::make_object(L, sol::lua_nil); --index; auto val = v[index]; switch (val.GetType()) { case JSON_INTEGER: return sol::make_object(L, int64_t(val)); case JSON_FLOAT: return sol::make_object(L, double(val)); case JSON_BOOLEAN: return sol::make_object(L, bool(val)); case JSON_STRING: return sol::make_object(L, std::string(val)); case JSON_ARRAY: case JSON_OBJECT: return sol::make_object(L, val); } } ), sol::meta_function::new_index, sol::overload( [](Json& v, int64_t index, double x) { --index; if (index < 0) return; if (index >= v.size()) v.resize(index + 1); v[index] = x; }, [](Json& v, std::string key, double x) { v[key] = x; }, [](Json& v, int64_t index, std::string x) { --index; if (index < 0) return; if (index >= v.size()) v.resize(index + 1); v[index] = x; }, [](Json& v, std::string key, std::string x) { v[key] = x; }, [](Json& v, int64_t index, bool x) { --index; if (index < 0) return; if (index >= v.size()) v.resize(index + 1); v[index] = x; }, [](Json& v, std::string key, bool x) { v[key] = x; }, [](Json& v, int64_t index, const Json& x) { --index; if (index < 0) return; if (index >= v.size()) v.resize(index + 1); v[index] = x; }, [](Json& v, std::string key, const Json& x) { v[key] = x; } ) ); L->set_function("JsonObject", Json::Object); L->set_function("JsonArray", Json::Array); auto j3 = Json::Object(); /*j3["health"] = 100; j3["windowsettings"] = Json::Object(); j3["windowsettings"]["position"] = 3; int gf = j3["windowsettings"]["position"]; int g = j3["health"]; for (auto a : j3) { Print(a.first); std::string s = a.second; Print(s); } auto arr = Json::Array(); arr.push_back(1); arr.push_back(2); arr.push_back(3); arr.push_back(4); arr.push_back(5); for (int n = 0; n < arr.size(); ++n) { Print(std::string(arr[n])); } Print(std::string(j3["health"])); */ //Get commandline settings auto settings = ParseCommandLine(argc, argv); //Enable the debugger if needed shared_ptr<Timer> debugtimer; if (settings["debug"].is_boolean() and settings["debug"] == true) { RunScript("Scripts/System/Debugger.lua"); debugtimer = CreateTimer(510); ListenEvent(EVENT_TIMERTICK, debugtimer, std::bind(PollDebugger, 500)); } //Run main script RunScript("Scripts/Main.lua"); return 0; }
  2. It's clear after working with this for a while that what is needed is a table-like class in C++ that is mostly interchangeable with JSON and can be quickly accessed by both C++ and Lua. The problem with JSON is that nodes can behave like STL maps or vectors, and when you try to derive from both those types there's a lot of problems binding it with sol. So I will paste this here before I go tearing it apart. enum JsonType { JSON_NULL, JSON_INTEGER, JSON_FLOAT, JSON_BOOLEAN, JSON_STRING, JSON_OBJECT, JSON_ARRAY }; class Json : private std::map<std::string, Json>, std::vector<Json> { typedef std::map<std::string, Json> JsonObject; typedef std::vector<Json> JsonArray; float f; int i; bool b; std::string s; JsonType t; friend JsonObject; friend JsonArray; public: Json() { f = 0; i = 0; b = false; t = JSON_NULL; } JsonObject::iterator begin() { return JsonObject::begin(); } JsonObject::iterator end() { return JsonObject::end(); } static Json Object() { Json j; j.t = JSON_OBJECT; return j; } static Json Array() { Json j; j.t = JSON_ARRAY; return j; } JsonType GetType() { return t; } operator int() { if (t == JSON_INTEGER) return i; if (t == JSON_FLOAT) return f; if (t == JSON_BOOLEAN) return b; return 0; } operator float() { if (t == JSON_FLOAT) return f; if (t == JSON_INTEGER) return i; if (t == JSON_BOOLEAN) return b; return 0.0f; } operator std::string() { if (t == JSON_STRING) return s; if (t == JSON_FLOAT) return String(f); if (t == JSON_INTEGER) return String(i); if (t == JSON_BOOLEAN) return String(b); return ""; } void clear() { i = 0; f = 0; b = false; s.clear(); JsonArray::clear(); JsonObject::clear(); } Json(const int i_) { clear(); i = i_; t = JSON_INTEGER; } Json(const bool b_) { clear(); b = b_; t = JSON_BOOLEAN; } Json(const float f_) { clear(); f = f_; t = JSON_FLOAT; } Json(const std::string& s_) { clear(); s = s_; t = JSON_STRING; } Json(const JsonObject& j3) { clear(); t = JSON_OBJECT; for (const auto& pair : j3) { JsonObject::insert(pair); } //JsonObject::insert(JsonObject::end(), j3.begin(), j3.end()); } Json(const JsonArray& j3) { clear(); t = JSON_ARRAY; JsonArray::insert(JsonArray::end(), j3.begin(), j3.end()); } Json& operator[](const std::string& key) { if (t != JSON_OBJECT) RuntimeError("JSON value is not an object"); auto it = JsonObject::find(key); if (it == JsonObject::end()) { auto pair = std::pair<std::string, Json>(key, {}); JsonObject::insert(pair); it = JsonObject::find(key); Assert(it != JsonObject::end()); } return it->second; } Json& operator[](const int index) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); return JsonArray::at(index); } void resize(const size_t sz) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); JsonArray::resize(sz); } void reserve(const size_t sz) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); JsonArray::reserve(sz); } size_t capacity() { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); return JsonArray::capacity(); } size_t size() { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); return JsonArray::size(); } void push_back(const Json& j3) { if (t != JSON_ARRAY) RuntimeError("JSON value is not an array"); JsonArray::push_back(j3); } }; Example usage: auto j3 = Json::Object(); j3["health"] = 100; j3["windowsettings"] = Json::Object(); j3["windowsettings"]["position"] = 3; int g = j3["health"]; for (auto a : j3) { Print(a.first); Print(std::string(a.second)); } auto arr = Json::Array(); arr.push_back(1); arr.push_back(2); arr.push_back(3); arr.push_back(4); arr.push_back(5); for (int n = 0; n < arr.size(); ++n) { Print(std::string(arr[n])); } Print(std::string(j3["health"]));
  3. 1.0.2 Removed nlohmann::json from Lua due to debugger problem here: https://github.com/devcat-studio/VSCodeLuaDebug/issues/30 I like the way it works but I may need to rethink the implementation.
  4. This will blow your mind: I was able to work out an API for complete control of Lua via C++. See release notes for details. This is very very powerful because the sol::object class can handle any C++ type, even new classes you just declared and haven't even bound with sol. This was always a problem in Leadwerks, and the reason why the C++/Lua interface was never fleshed out. The new CallFunction function can handle any number of and type of arguments, and supports multiple return values. You can get and set Lua fields for every class derived from Object, and even call method-style functions with CallMethod, and the object passed to Lua will be whatever type you pass to the function. This was impossible in Leadwerks, and everything just got cast to Object or Entity and had to be re-cast in Lua if you wanted to access a derived class. You can declare a C++ class, bind it to Lua, pass it to a function, and immediately start using it in Lua, without touching the Lua API. This is how I always pictured how an interface between C++ and Lua should work, but it was never possible until now. #include "UltraEngine.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Create an object auto box = CreateBox(NULL); box->name = "Bob"; //Declare a variable in Lua SetGlobal("entity", box); //Run a script that attaches a function to the entity ExecuteString("function entity:Rename( newname ) self.name = newname end"); //Call the method CallMethod(box, "Rename", { "Fred" }); //Check if it worked Print(box->name); return 0; } This will also work: //Run a script that attaches a function to the entity ExecuteString("function _G:Rename( newname ) self.name = newname end"); //Call the method CallFunction("Rename", { box, "Fred" }); This works absolutely fine: class SomeClassIJustMadeUp { int idk = 1; }; SomeClassIJustMadeUp foo; SetGlobal("something", foo);
  5. 1.0.2 Added C++ control over Lua with new functions and methods: SetGlobal GetGlobal CallFunction CallMethod Object::SetField Object::GetField
  6. The C implementation of variadic functions is really bad. You have to specify the number of parameters, which makes it more code than just passing a vector.
  7. JSON example. The only thing missing are key/value pairs. The reason this is so cool is because it allows Lua to modify JSON data stored in C++ without having to pass the entire structure back and forth between nlohmann::json and Lua for each change. So this can be used to read and write values to scene files, insert custom user data into glTF files, and add custom user settings in the editor. local j3array = JsonArray() j3array[1] = "test value 1" j3array[2] = "test value 2" j3array[3] = "test value 3" j3array[4] = "test value 4" j3array[5] = "test value 5" --Array for n = 1, #j3array do Print(tostring(n)..": "..j3array[n]) end --iPairs for k, v in ipairs(j3array) do Print(tostring(k)..": "..tostring(v)) end local j3object = Json() j3object["color"] = "blue" j3object["weight"] = 100 j3object["happy"] = true --Pairs for k, v in pairs(j3object) do Print(tostring(k)..": "..tostring(v)) end The Lua component save / load methods can work just like C++: function component:Save(J3) j3["health"] = self.health return true end Of course with Lua that can be pre-serialized, and then the function would just be used for any adjustments you want to make, like saving data of loaded resources, and other things that don't get saved automatically. JSON objects can only store numbers, strings, booleans, and other JSON objects, so they can always be saved to a file and loaded back the same.
  8. You can use Lua and JSON together now. The same JSON objects are accessible to both C++ and Lua with no fiddling around with stacks and things: local j3 = Json() j3.key1 = "value" j3.key2 = 3 j3["key3"] = true Print(tostring(j3)) This prints out: { "key1": "value", "key2": 3, "key3": true } LoadJson() and SaveJson() are also available. This is how I plan on handling user-defined settings in the editor: program.settings.mainwindow.position.x = 50 program.settings.mainwindow.position.y = 100 That will directly modify the program->settings json object and those changes will be saved to the settings file. I'm shocked that it worked so neatly. Other stuff to try: https://www.ultraengine.com/learn/Component_Connect?lang=lua https://www.ultraengine.com/learn/Component_Collide?lang=lua
  9. 1.0.2 Added Component:Connect in Lua Integrated nlohmann::json into Lua(!). Json structures can be easily accessed by both C++ and Lua, and changes in one environment are instantly available in the other. Removed the ability to add a component from a table, as this was causing problems. Components must be modules.
  10. Adding Component:Connect in Lua. This gives you the ability to create flowgraph connections in code, with any number of arbitrary arguments, including tables, functions, and other special Lua stuff: sender:Connect(sender.Send, receiver, receiver.Receive, { true, 2, "Hello!" }) Whenever the send method is called, whether it is by code or another flowgraph input, at the end of it the receiver will be triggered to execute the Receive method, with the supplied arguments. https://www.ultraengine.com/learn/Component_Connect?lang=lua
  11. Apparently not. It's okay, I don't think I am going to use this anyways.
  12. Every example I can find online uses a chr*. You can't call ++ on an std::any. Does anyone know how to correctly iterate this to get all the arguments?: bool LuaComponent::CallMethod(const WString& name, const std::any& args...) { std::vector<std::any> v; auto vargs = va_list(); va_start(vargs, args); while (args.has_value()) { v.push_back(args); args++;//??? this does not compile } va_end(vargs); return CallMethod(name, v); }
  13. I think that's no problem. It's mostly just a matter of deciding how the user interface should work. Lua Collide function is working now. Here's an example: https://www.ultraengine.com/learn/Component_Collide?lang=lua
  14. You can now create Lua components from a table as well: local component = {} function component:Update() self.entity:Turn(10,0,0) end box:AddComponent(component, "MyComponent") You need to go through the AddComponent() command, not just add tables to your entity, because this creates the C++ component and handles a bunch of stuff. I did this primarily for ease of showing things in the documentation. If a special component is needed it's easy to just add it into the documentation example. It looks like std::any will probably work for functions-as-arguments as well: int FUNC() { return 3; } int main(int argc, const char* argv[]) { std::any thing = FUNC; Print(thing.type().name()); auto f = std::any_cast<int(__cdecl*)(void)>(thing); Print(f()); return 0; }
  15. 1.0.2 Component connections are added, with arguments. Arguments won't work in Lua yet, but connections should. Added LuaGetState
  16. Component connections are added now, with arguments. Here is a working example. A lot of stuff has to be defined manually in C++. In Lua this is all automated, including the output firing: #include "UltraEngine.h" using namespace UltraEngine; struct Sender : public Component { virtual void MyMethod() { //---------------------------------------------------- // You probably want to execute some other code here //---------------------------------------------------- Print("Firing output MyMethod"); FireOutputs("MyMethod"); } }; struct Receiver : public Component { virtual void AnotherMethod(const int i) { Print("Executing method with argument: " + String(i)); //---------------------------------------------------- // You probably want to execute some other code here //---------------------------------------------------- } virtual void ReceiveSignal(shared_ptr<Component> sender, const WString& input, std::vector<std::any> args) { Print("Signal received: " + input); //Choose the method to call if (input == "AnotherMethod") { //Fix arguments args.resize(1); if (strcmp(args[0].type().name(), "int") != 0) args[0] = 0; //Call the method AnotherMethod(std::any_cast<int>(args[0])); } else { Print("Unknown method " + input); } } }; int main(int argc, const char* argv[]) { //Create sending object auto box1 = CreateBox(NULL); auto sender = box1->AddComponent<Sender>(); //Create receiving object auto box2 = CreateBox(NULL); auto receiver = box2->AddComponent<Receiver>(); //Add connection sender->Connect("MyMethod", receiver, "AnotherMethod", { 3 }); //Trigger the connection sender->MyMethod(); return 0; } I think the same thing in Lua will work like this, without any manual processing or firing: Sender = {} function Sender:MyMethod() Print("Firing output MyMethod"); end Receiver = {} function Receiver:AnotherMethod(i) Print("Executing method with argument: "..tostring(i)) end --Create sending object auto box1 = CreateBox(NULL) auto sender = box1->AddComponent(Sender) --Create receiving object auto box2 = CreateBox(NULL) auto receiver = box2->AddComponent(Receiver) --Add connection sender->Connect(Sender.MyMethod, receiver, Receiver.AnotherMethod, { 3 }) --Trigger the connection sender->MyMethod()
  17. Here's a quick test using std::any: #include "UltraEngine.h" #include <any> using namespace UltraEngine; int main(int argc, const char* argv[]) { std::any thing = 3; //thing = String("test"); //thing = 5u; if (thing.has_value()) { auto name = String(thing.type().name()); if (name == "int") { Print(std::any_cast<int>(thing)); } else if (name == "float") { Print(std::any_cast<float>(thing)); } else if (name == "class UltraEngine::String") { Print(std::any_cast<String>(thing)); } else { Print("Unknown type \"" + name + "\""); } } else { Print("thing is empty."); } return 0; }
  18. I've worked out the basic flowgraph system: https://www.ultraengine.com/learn/Component_Connect?lang=cpp https://www.ultraengine.com/learn/Component_FireOutputs?lang=cpp https://www.ultraengine.com/learn/Component_ReceiveSignal?lang=cpp I don't know yet exactly how std::any works, or how functions as arguments will work. I doubt I can really work that out until the visual interface is usable, but the basic implementation is done.
  19. The Lua component system stuff is now stored in a single table: ComponentSystem = {}-- global variable ComponentSystem.mode = "ECS"-- default behavior --ComponentSystem.mode = "OOP"-- experimental, will break compatibility with ECS-style scripts --This function is called when a component is attached to an entity function ComponentSystem:AddComponent(entity, component, name) local table = require("Components."..name) if table == nil then return false end blah blah blah... The script itself is now responsible for the execution of the component script. It uses require() so the component script only gets executed once and the same table's values are copied to the C++ component object each time. Since it uses require() it expects a Lua module name, not a script name, so the usage will look like this: entity:AddComponent("Mover") I plan to organize components into subfolders one layer deep, so the final usage will be more like this: entity:AddComponent("Motion.Mover") I have no idea how or if this will interact with C#. What will the user syntax to add a C# component look like? Will it use a string like this, or something else? Do we plan on Lua ever working with a C# game? I'm leaning towards no, because C# users aren't going to be coming from that mindset or have that expectation.
  20. 1.0.2 "require" will now load scripts relative to "./Scripts" and DLLs relative to "./Modules". Script structure in the game template is changed. C++ interpreter for Lua code is updated here: https://github.com/UltraEngine/Documentation/blob/master/CPP/Scripting.md#c-interpreter-for-lua socket/core.dll now just renamed to "luasocket.dll" Compiled .luac files can now be run with RunScript().
  21. 1.0.2 I found a lot of settings to move all the garbage files VS creates into the hidden .vs folder. This is what a project looks like now, after compiling and closing visual studio. The only file it save in your game folder is the vxproj.user file, which I could not find a way to eliminate:
  22. The secret is that the icon file must contain multiple "mipmaps". It's not enough for it to be a large icon. This program is the only way I know to create these: http://www.aha-soft.com/anytoicon/
  23. Why is Windows Explorer showing my EXE as if it has a small icon? The icon size is 256x256 and the icon file itself appears correctly: Here is the icon section of the .rc resource file: ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "projecticon.ico"
  24. 1.0.2 UPX post-build process is added back into new Visual Studio projects, but this time it's done correctly. Command: "$(UltraEnginePath)\Tools\upx.exe" "$(TargetName)$(TargetExt)" Disabled by default, can be easily enabled in project settings (release mode only) Newton debug DLLs are eliminated. I'm using a static library, so Newton can still be debugged externally. DLLs are now only used for optional plugins and Lua modules. Resource files moved to /Source to try to keep as much garbage as possible out of the main folder. Removed Warn() function. Removed Microsecs() function.
  25. 1.0.2 Since the Lua stuff is stable now, I was able to compile Lua as a static lib, which means no DLL is needed. Lua 5.4 repo here is updated: https://github.com/UltraEngine/Lua5.4 Scripts/Modules/Socket/core.dll is recompiled with Lua static lib.
×
×
  • Create New...