diff --git a/core/src/datatypes/base.cpp b/core/src/datatypes/base.cpp index b3519f8..92ca236 100644 --- a/core/src/datatypes/base.cpp +++ b/core/src/datatypes/base.cpp @@ -1,4 +1,5 @@ #include "base.h" +#include "error/data.h" #include "meta.h" #include #include @@ -7,7 +8,12 @@ #define IMPL_WRAPPER_CLASS(CLASS_NAME, WRAPPED_TYPE, TYPE_NAME) Data::CLASS_NAME::CLASS_NAME(WRAPPED_TYPE in) : value(in) {} \ Data::CLASS_NAME::~CLASS_NAME() = default; \ Data::CLASS_NAME::operator const WRAPPED_TYPE() const { return value; } \ -const Data::TypeInfo Data::CLASS_NAME::TYPE = { .name = TYPE_NAME, .deserializer = &Data::CLASS_NAME::Deserialize, .fromString = &Data::CLASS_NAME::FromString }; \ +const Data::TypeInfo Data::CLASS_NAME::TYPE = { \ + .name = TYPE_NAME, \ + .deserializer = &Data::CLASS_NAME::Deserialize, \ + .fromString = &Data::CLASS_NAME::FromString, \ + .fromLuaValue = &Data::CLASS_NAME::FromLuaValue, \ +}; \ const Data::TypeInfo& Data::CLASS_NAME::GetType() const { return Data::CLASS_NAME::TYPE; }; \ void Data::CLASS_NAME::Serialize(pugi::xml_node node) const { node.text().set(std::string(this->ToString())); } @@ -37,6 +43,10 @@ void Data::Null::PushLuaValue(lua_State* L) const { lua_pushnil(L); } +result Data::Null::FromLuaValue(lua_State* L, int idx) { + return Data::Variant(Data::Null()); +} + // IMPL_WRAPPER_CLASS(Bool, bool, "bool") @@ -122,4 +132,30 @@ void Data::Float::PushLuaValue(lua_State* L) const { void Data::String::PushLuaValue(lua_State* L) const { lua_pushstring(L, value.c_str()); +} + +// FromLuaValue + +result Data::Bool::FromLuaValue(lua_State* L, int idx) { + if (!lua_isboolean(L, idx)) + return LuaCastError(lua_typename(L, idx), "boolean"); + return Data::Variant(Data::Bool(lua_toboolean(L, idx))); +} + +result Data::Int::FromLuaValue(lua_State* L, int idx) { + if (!lua_isnumber(L, idx)) + return LuaCastError(lua_typename(L, idx), "integer"); + return Data::Variant(Data::Int((int)lua_tonumber(L, idx))); +} + +result Data::Float::FromLuaValue(lua_State* L, int idx) { + if (!lua_isnumber(L, idx)) + return LuaCastError(lua_typename(L, idx), "float"); + return Data::Variant(Data::Float((float)lua_tonumber(L, idx))); +} + +result Data::String::FromLuaValue(lua_State* L, int idx) { + if (!lua_tostring(L, idx)) + return LuaCastError(lua_typename(L, idx), "string"); + return Data::Variant(Data::String(lua_tostring(L, idx))); } \ No newline at end of file diff --git a/core/src/datatypes/base.h b/core/src/datatypes/base.h index 743ad3c..ab38d3f 100644 --- a/core/src/datatypes/base.h +++ b/core/src/datatypes/base.h @@ -4,6 +4,8 @@ #include #include #include +#include "error/result.h" +#include "error/data.h" extern "C" { typedef struct lua_State lua_State; } @@ -22,17 +24,20 @@ public: \ \ static Data::Variant Deserialize(pugi::xml_node node); \ static std::optional FromString(std::string); \ + static result FromLuaValue(lua_State*, int idx); \ }; namespace Data { class Variant; typedef std::function Deserializer; typedef std::function(std::string)> FromString; + typedef std::function(lua_State*, int idx)> FromLuaValue; struct TypeInfo { std::string name; Deserializer deserializer; FromString fromString; + FromLuaValue fromLuaValue; }; class String; @@ -57,6 +62,7 @@ namespace Data { virtual void PushLuaValue(lua_State*) const override; static Data::Variant Deserialize(pugi::xml_node node); + static result FromLuaValue(lua_State*, int idx); }; DEF_WRAPPER_CLASS(Bool, bool) diff --git a/core/src/datatypes/meta.cpp b/core/src/datatypes/meta.cpp index 01cf5a3..d83c56f 100644 --- a/core/src/datatypes/meta.cpp +++ b/core/src/datatypes/meta.cpp @@ -1,6 +1,7 @@ #include "meta.h" #include "datatypes/base.h" #include "datatypes/cframe.h" +#include "datatypes/ref.h" #include "logger.h" #include "panic.h" #include @@ -25,7 +26,7 @@ void Data::Variant::PushLuaValue(lua_State* state) const { Data::Variant Data::Variant::Deserialize(pugi::xml_node node) { if (Data::TYPE_MAP.count(node.name()) == 0) { - Logger::fatalErrorf("Unknown type for instance: '%s'", node.name()); + Logger::fatalErrorf("Unknown type for property: '%s'", node.name()); panic(); } @@ -42,4 +43,5 @@ std::map Data::TYPE_MAP = { { "Vector3", &Data::Vector3::TYPE }, { "CoordinateFrame", &Data::CFrame::TYPE }, { "Color3", &Data::Color3::TYPE }, + { "Ref", &Data::InstanceRef::TYPE }, }; \ No newline at end of file diff --git a/core/src/datatypes/ref.cpp b/core/src/datatypes/ref.cpp index 616a537..8a1d6eb 100644 --- a/core/src/datatypes/ref.cpp +++ b/core/src/datatypes/ref.cpp @@ -1,11 +1,13 @@ #include "datatypes/ref.h" #include "datatypes/base.h" +#include "error/data.h" #include "logger.h" #include "meta.h" // IWYU pragma: keep #include #include #include "objects/base/instance.h" #include "lua.h" +#include "objects/base/member.h" Data::InstanceRef::InstanceRef() {}; Data::InstanceRef::InstanceRef(std::weak_ptr instance) : ref(instance) {}; @@ -13,7 +15,8 @@ Data::InstanceRef::~InstanceRef() = default; const Data::TypeInfo Data::InstanceRef::TYPE = { .name = "Instance", - // .deserializer = &Data::InstanceRef::Deserialize, + .deserializer = &Data::InstanceRef::Deserialize, + .fromLuaValue = &Data::InstanceRef::FromLuaValue, }; const Data::TypeInfo& Data::InstanceRef::GetType() const { return Data::InstanceRef::TYPE; }; @@ -32,15 +35,17 @@ void Data::InstanceRef::Serialize(pugi::xml_node node) const { // node.text().set(this->ToHex()); } -// Data::Variant Color3::Deserialize(pugi::xml_node node) { -// return Color3::FromHex(node.text().get()); -// } +Data::Variant Data::InstanceRef::Deserialize(pugi::xml_node node) { + return Data::InstanceRef(); +} static int inst_gc(lua_State*); static int inst_index(lua_State*); +static int inst_newindex(lua_State*); static const struct luaL_Reg metatable [] = { {"__gc", inst_gc}, {"__index", inst_index}, + {"__newindex", inst_newindex}, {NULL, NULL} /* end of array */ }; @@ -62,6 +67,16 @@ void Data::InstanceRef::PushLuaValue(lua_State* L) const { lua_setmetatable(L, n+1); } +result Data::InstanceRef::FromLuaValue(lua_State* L, int idx) { + if (lua_isnil(L, idx)) + return Data::Variant(Data::InstanceRef()); + if (!lua_isuserdata(L, idx)) + return LuaCastError(lua_typename(L, idx), "Instance"); + // TODO: overhaul this to support other types + auto userdata = (std::shared_ptr**)lua_touserdata(L, idx); + return Data::Variant(Data::InstanceRef(**userdata)); +} + static int inst_gc(lua_State* L) { // Destroy the contained shared_ptr auto userdata = (std::shared_ptr**)lua_touserdata(L, -1); @@ -71,6 +86,7 @@ static int inst_gc(lua_State* L) { return 0; } +// __index(t,k) static int inst_index(lua_State* L) { auto userdata = (std::shared_ptr**)lua_touserdata(L, 1); std::shared_ptr inst = **userdata; @@ -93,4 +109,28 @@ static int inst_index(lua_State* L) { } return luaL_error(L, "'%s' is not a valid member of %s", key.c_str(), inst->GetClass()->className.c_str()); +} + +// __newindex(t,k,v) +static int inst_newindex(lua_State* L) { + auto userdata = (std::shared_ptr**)lua_touserdata(L, 1); + std::shared_ptr inst = **userdata; + std::string key(lua_tostring(L, 2)); + + // Validate property + std::optional meta = inst->GetPropertyMeta(key); + if (!meta) + return luaL_error(L, "'%s' is not a valid member of %s", key.c_str(), inst->GetClass()->className.c_str()); + if (meta->flags & PROP_READONLY) + return luaL_error(L, "'%s' of %s is read-only", key.c_str(), inst->GetClass()->className.c_str()); + if (key == "Parent" && inst->IsParentLocked()) + return luaL_error(L, "Cannot set property Parent (%s) of %s, parent is locked", inst->GetParent() ? inst->GetParent().value()->name.c_str() : "NULL", inst->GetClass()->className.c_str()); + + result value = meta->type->fromLuaValue(L, -1); + lua_pop(L, 3); + + if (value.isError()) + return luaL_error(L, "%s", value.errorMessage().value().c_str()); + inst->SetPropertyValue(key, value.expect()).expect(); + return 0; } \ No newline at end of file diff --git a/core/src/datatypes/ref.h b/core/src/datatypes/ref.h index 00bac3f..3b812aa 100644 --- a/core/src/datatypes/ref.h +++ b/core/src/datatypes/ref.h @@ -1,6 +1,7 @@ #pragma once #include "base.h" +#include "error/data.h" #include class Instance; @@ -21,6 +22,7 @@ namespace Data { virtual const Data::String ToString() const override; virtual void Serialize(pugi::xml_node node) const override; virtual void PushLuaValue(lua_State*) const override; - // static Data::Variant Deserialize(pugi::xml_node node); + static Data::Variant Deserialize(pugi::xml_node node); + static result FromLuaValue(lua_State*, int idx); }; } diff --git a/core/src/error/data.h b/core/src/error/data.h new file mode 100644 index 0000000..c2a58e1 --- /dev/null +++ b/core/src/error/data.h @@ -0,0 +1,8 @@ +#pragma once + +#include "error.h" + +class LuaCastError : public Error { + public: + inline LuaCastError(std::string sourceType, std::string targetType) : Error("InstanceCastError", "Attempt to cast " + sourceType + " to " + targetType) {} +}; \ No newline at end of file diff --git a/core/src/error/result.h b/core/src/error/result.h index b643123..034dbbd 100644 --- a/core/src/error/result.h +++ b/core/src/error/result.h @@ -52,6 +52,13 @@ public: }, error().value()); } + std::optional errorMessage() { + if (isSuccess()) return std::nullopt; + return std::visit([&](auto&& it) { + return it.message(); + }, error().value()); + } + // Equivalent to .success operator std::optional() { return success(); } operator bool() { return isSuccess(); } diff --git a/core/src/objects/base/instance.cpp b/core/src/objects/base/instance.cpp index dde7c7f..2bac961 100644 --- a/core/src/objects/base/instance.cpp +++ b/core/src/objects/base/instance.cpp @@ -48,6 +48,7 @@ Instance::Instance(const InstanceType* type) { .super = std::nullopt, .members = { { "Name", { .backingField = &name, .type = &Data::String::TYPE, .codec = fieldCodecOf() } }, + { "Parent", { .backingField = &parent, .type = &Data::InstanceRef::TYPE, .codec = fieldCodecOf>() } }, { "ClassName", { .backingField = const_cast(type), .type = &Data::String::TYPE, .codec = classNameCodec(), .flags = (PropertyFlags)(PROP_READONLY | PROP_NOSAVE) } }, } }); @@ -202,6 +203,14 @@ result Instance::GetPropertyValue(std::string nam } fallible Instance::SetPropertyValue(std::string name, Data::Variant value) { + // Handle special case: Parent + if (name == "Parent") { + Data::InstanceRef ref = value.get(); + std::weak_ptr inst = ref; + SetParent(inst.expired() ? std::nullopt : std::make_optional(inst.lock())); + return {}; + } + auto meta_ = GetPropertyMeta(name); if (!meta_) return MemberNotFound(GetClass()->className, name); auto meta = meta_.expect(); diff --git a/editor/panes/explorerview.cpp b/editor/panes/explorerview.cpp index 8a80267..4c62138 100644 --- a/editor/panes/explorerview.cpp +++ b/editor/panes/explorerview.cpp @@ -7,6 +7,7 @@ #include "objects/script.h" #include #include +#include #define M_mainWindow dynamic_cast(window()) @@ -74,10 +75,11 @@ void ExplorerView::keyPressEvent(QKeyEvent* event) { void ExplorerView::mouseDoubleClickEvent(QMouseEvent *event) { QModelIndex index = indexAt(event->pos()); std::shared_ptr inst = model.fromIndex(index); - if (!inst->IsA