feat(lua): deserializing values and setting instance properties

This commit is contained in:
maelstrom 2025-04-25 11:32:11 +02:00
parent cbed2bac95
commit 9be6c103de
9 changed files with 120 additions and 8 deletions

View file

@ -1,4 +1,5 @@
#include "base.h"
#include "error/data.h"
#include "meta.h"
#include <ios>
#include <sstream>
@ -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::Variant, LuaCastError> Data::Null::FromLuaValue(lua_State* L, int idx) {
return Data::Variant(Data::Null());
}
//
IMPL_WRAPPER_CLASS(Bool, bool, "bool")
@ -123,3 +133,29 @@ 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::Variant, LuaCastError> 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::Variant, LuaCastError> 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::Variant, LuaCastError> 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::Variant, LuaCastError> 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)));
}

View file

@ -4,6 +4,8 @@
#include <functional>
#include <optional>
#include <pugixml.hpp>
#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<Data::Variant> FromString(std::string); \
static result<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx); \
};
namespace Data {
class Variant;
typedef std::function<Data::Variant(pugi::xml_node)> Deserializer;
typedef std::function<std::optional<Data::Variant>(std::string)> FromString;
typedef std::function<result<Data::Variant, LuaCastError>(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<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
DEF_WRAPPER_CLASS(Bool, bool)

View file

@ -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 <variant>
@ -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<std::string, const Data::TypeInfo*> Data::TYPE_MAP = {
{ "Vector3", &Data::Vector3::TYPE },
{ "CoordinateFrame", &Data::CFrame::TYPE },
{ "Color3", &Data::Color3::TYPE },
{ "Ref", &Data::InstanceRef::TYPE },
};

View file

@ -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 <memory>
#include <optional>
#include "objects/base/instance.h"
#include "lua.h"
#include "objects/base/member.h"
Data::InstanceRef::InstanceRef() {};
Data::InstanceRef::InstanceRef(std::weak_ptr<Instance> 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::Variant, LuaCastError> 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<Instance>**)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<Instance>**)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<Instance>**)lua_touserdata(L, 1);
std::shared_ptr<Instance> inst = **userdata;
@ -94,3 +110,27 @@ 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<Instance>**)lua_touserdata(L, 1);
std::shared_ptr<Instance> inst = **userdata;
std::string key(lua_tostring(L, 2));
// Validate property
std::optional<PropertyMeta> 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<Data::Variant, LuaCastError> 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;
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "base.h"
#include "error/data.h"
#include <memory>
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<Data::Variant, LuaCastError> FromLuaValue(lua_State*, int idx);
};
}

8
core/src/error/data.h Normal file
View file

@ -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) {}
};

View file

@ -52,6 +52,13 @@ public:
}, error().value());
}
std::optional<std::string> errorMessage() {
if (isSuccess()) return std::nullopt;
return std::visit([&](auto&& it) {
return it.message();
}, error().value());
}
// Equivalent to .success
operator std::optional<T_Result>() { return success(); }
operator bool() { return isSuccess(); }

View file

@ -48,6 +48,7 @@ Instance::Instance(const InstanceType* type) {
.super = std::nullopt,
.members = {
{ "Name", { .backingField = &name, .type = &Data::String::TYPE, .codec = fieldCodecOf<Data::String, std::string>() } },
{ "Parent", { .backingField = &parent, .type = &Data::InstanceRef::TYPE, .codec = fieldCodecOf<Data::InstanceRef, std::weak_ptr<Instance>>() } },
{ "ClassName", { .backingField = const_cast<InstanceType*>(type), .type = &Data::String::TYPE, .codec = classNameCodec(), .flags = (PropertyFlags)(PROP_READONLY | PROP_NOSAVE) } },
}
});
@ -202,6 +203,14 @@ result<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string nam
}
fallible<MemberNotFound, AssignToReadOnlyMember> Instance::SetPropertyValue(std::string name, Data::Variant value) {
// Handle special case: Parent
if (name == "Parent") {
Data::InstanceRef ref = value.get<Data::InstanceRef>();
std::weak_ptr<Instance> 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();

View file

@ -7,6 +7,7 @@
#include "objects/script.h"
#include <memory>
#include <qaction.h>
#include <qtreeview.h>
#define M_mainWindow dynamic_cast<MainWindow*>(window())
@ -74,10 +75,11 @@ void ExplorerView::keyPressEvent(QKeyEvent* event) {
void ExplorerView::mouseDoubleClickEvent(QMouseEvent *event) {
QModelIndex index = indexAt(event->pos());
std::shared_ptr<Instance> inst = model.fromIndex(index);
if (!inst->IsA<Script>()) return;
if (!inst->IsA<Script>()) return QTreeView::mouseDoubleClickEvent(event);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
mainWnd->openScriptDocument(inst->CastTo<Script>().expect());
QTreeView::mouseDoubleClickEvent(event);
}
void ExplorerView::buildContextMenu() {