Jump to content

Josh

Administrators
  • Posts

    23,112
  • Joined

  • Last visited

Everything posted by Josh

  1. And here that is in action:
  2. 1.0.2 Editor updated with file thumbnails working again. Thumbnail generation is not asynchronous yet, and currently only displays whatever image Windows Explorer shows. Fixed the problem with upside-down thumbnails. Editor scripts folder updated with all the recent changes in the Lua system. Files in packages currently do not show thumbnail images. Fixed window size save setting bug. I recommend uninstalling and reinstalling 1.0.2.
  3. Updated to fix flipped images.
  4. 1.0.2 Engine properties now only get saved in glTF files if they deviate from the default values. If those default values change in the future, your existing models won't be stuck using the old defaults. Lua table to C++ table conversion now accounts for infinite recursion (a table with itself as a member). C++ table to JSON conversion does NOT account for this and will cause a stack overflow error. With that whole aspect completed, I will now turn my attention back to the new editor...
  5. 1.0.2 Entity and materials property field now using tables instead of nlohmann::json object. glTF files will now save and load user properties. [] symbol and ctable() Lua function removed. There is no reason to ever create C++ tables in Lua. They will only be used as members of a class that has already been declared in C++ and exposed to Lua. You can now assign a Lua table to a C++ table, and it will just copy the contents. C++ example auto box = CreateBox(NULL); box->properties["health"] = 100; box->Save(GetPath(PATH_DESKTOP) + "/box.gltf"); auto box2 = LoadModel(NULL, GetPath(PATH_DESKTOP) + "/box.gltf"); Print(box2->properties["health"]); Lua example local box = CreateBox(nil) box.properties.health = 100 box:Save(GetPath(PATH_DESKTOP).."/box.gltf") local box2 = LoadModel(nil, GetPath(PATH_DESKTOP).."/box.gltf") Print(box2.properties.health) glTF nodes section looks like below. You can see the engine has its properties it saves, and the user's properties are completely separate. "nodes": [ { "extras": { "ultra": { "angularDamping": 0.10000000149011612, "collisionType": 1, "elasticity": 0.4000000059604645, "kineticFriction": 0.5, "linearDamping": 0.10000000149011612, "navigationObstacle": true, "physicsMode": 1, "pickmode": 2, "staticFriction": 0.8999999761581421 }, "user": { "health": 100 } }, "mesh": 0 } ], The saved file is still a valid glTF file that can be loaded in Blender or another program. In fact, to my surprise, custom properties even survive being imported into Blender and exported again, if you have the "custom properties" option checked: This means you can pull model back into Blender, modify them, and save again without having to redo all your custom properties.
  6. I'm very very happy how this has turned out. All of these interrelated aspects of the software have been resolved in a consistent manner with an overarching design: Lua debugging Entity components between C++, Lua, and an exploratory attempt to expose components to C# Underlying code that powers flowgraph connections, with arguments accounted for Lua API for C++ Accessing engine and user-defined C++ classes in Lua Editor extensions Custom user data embedded in glTF, scene, and material files Application settings and user-defined settings in editor extensions accessible from both C++ and Lua with no weirdness Storing and loading data to and from JSON files Scene serialization for game saves If you don't understand the things I have been talking about in the last week, I don't blame you, but trust me that this is going to empower the engine and editor to do some really wonderful things, and provide the basis for a great scripting and extensions ecosystem for the new engine. I don't think I have ever worked on something this complex and yet it all came together so neatly. I feel like I have reached a new level of development / design / engineering with this. It will take a while for people to understand what it is, but you will love it. I just updated the engine with table.to_json() (outputs a string) and a table constructor that accepts an nlohman::json object (because I'm not going to write a JSON parser). Here is a demonstration. The output is as you would expect: table t; //Pure integer keys get output as a JSON array for (int n = 0; n < 10; ++n) { t[n] = n; } Print(t.to_json()); Print("\n-------------------------------------\n"); //Mix string and integer keys t["health"] = 100; t["money"] = 0; t["nullvalue"] = nullptr; t["zvalue"] = true; t["subtable"] = {}; t["subtable"]["position"] = 50; t["subtable"]["size"] = 300; t["subtable1"] = t["subtable"]; t["subarray"] = {}; t["subarray"][0] = 1; t["subarray"][1] = 2; t["subarray"][2] = 3; Print(t.to_json()); Print("\n-------------------------------------\n"); // Save and reload test auto stream = CreateBufferStream(); stream->WriteString(t.to_json(), false); stream->Seek(0); auto j3 = LoadJson(stream); table t2 = j3; Print(t2.to_json());
  7. 1.0.2 Update to fix this bug in VS Code Lia debugger.
  8. 1.0.2 Updated with what I think is the finished table++ code. Works with ipairs() now. The Lua command for a C++ table is now ctable(). Experimental Lua pre-processor feature uses [] to create a C++ table, like how {} creates a Lua table: b = [] b[1] = 1 b[2] = 2 b[3] = 3 for k, v in ipairs(b) do Print(v) end
  9. 1.0.2 Added my own STL-implementation of Lua tables in C++. C++ tables can be accessed by Lua but don't use Lua code to operate. This provides similar functionality to my earlier attempt to integrate nlohmann::json into Lua, without crashing the debugger.
  10. I got everything working except ipairs() in Lua. Further work will go here: https://github.com/UltraEngine/tableplusplus
  11. 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; }
  12. 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"]));
  13. 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.
  14. 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);
  15. 1.0.2 Added C++ control over Lua with new functions and methods: SetGlobal GetGlobal CallFunction CallMethod Object::SetField Object::GetField
  16. 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.
  17. 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.
  18. 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
  19. 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.
  20. 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
  21. Apparently not. It's okay, I don't think I am going to use this anyways.
  22. 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); }
  23. 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
  24. 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; }
  25. 1.0.2 Component connections are added, with arguments. Arguments won't work in Lua yet, but connections should. Added LuaGetState
×
×
  • Create New...