Skip to content

Commit 205d3bc

Browse files
authored
Merge pull request #883 from UE4SS-RE/LuaTSetSupport
feat(lua): adds support for `FSetProperty` in Lua scripting
2 parents 20f0f94 + c93966a commit 205d3bc

28 files changed

+655
-2
lines changed

UE4SS/include/LuaType/LuaTSet.hpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#pragma once
2+
3+
#include <LuaType/LuaUObject.hpp>
4+
#pragma warning(disable : 4005)
5+
#include <Unreal/Core/Containers/Set.hpp>
6+
#pragma warning(default : 4005)
7+
8+
namespace RC::Unreal
9+
{
10+
class UObject;
11+
class FProperty;
12+
class FSetProperty;
13+
}
14+
15+
namespace RC::LuaType
16+
{
17+
struct PusherParams;
18+
19+
struct TSetName
20+
{
21+
constexpr static const char* ToString()
22+
{
23+
return "TSet";
24+
}
25+
};
26+
27+
class TSet : public RemoteObjectBase<Unreal::FScriptSet, TSetName>
28+
{
29+
private:
30+
Unreal::UObject* m_base;
31+
Unreal::FSetProperty* m_property;
32+
Unreal::FProperty* m_element_property;
33+
34+
private:
35+
explicit TSet(const PusherParams&);
36+
37+
public:
38+
TSet() = delete;
39+
auto static construct(const PusherParams&) -> const LuaMadeSimple::Lua::Table;
40+
auto static construct(const LuaMadeSimple::Lua&, BaseObject&) -> const LuaMadeSimple::Lua::Table;
41+
42+
private:
43+
auto static setup_metamethods(BaseObject&) -> void;
44+
45+
private:
46+
template <LuaMadeSimple::Type::IsFinal is_final>
47+
auto static setup_member_functions(const LuaMadeSimple::Lua::Table&) -> void;
48+
49+
enum class SetOperation
50+
{
51+
Add,
52+
Contains,
53+
Remove,
54+
Empty,
55+
ForEach
56+
};
57+
58+
auto static prepare_to_handle(SetOperation, const LuaMadeSimple::Lua&) -> void;
59+
};
60+
61+
struct FScriptSetInfo
62+
{
63+
Unreal::FProperty* element{};
64+
Unreal::FName element_fname{};
65+
Unreal::FScriptSetLayout layout{};
66+
67+
FScriptSetInfo(Unreal::FProperty* element);
68+
69+
/**
70+
* Validates existence of lua pushers for this element in this structure.
71+
* Throws if a pusher for the element was not found
72+
*
73+
* @param lua Lua state to throw against.
74+
*/
75+
void validate_pushers(const LuaMadeSimple::Lua& lua);
76+
};
77+
}

UE4SS/include/LuaType/LuaUObject.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ namespace RC::LuaType
374374
RC_UE4SS_API auto push_uint64property(const PusherParams&) -> void;
375375
RC_UE4SS_API auto push_structproperty(const PusherParams&) -> void;
376376
RC_UE4SS_API auto push_arrayproperty(const PusherParams&) -> void;
377+
RC_UE4SS_API auto push_setproperty(const PusherParams&) -> void;
377378
RC_UE4SS_API auto push_mapproperty(const PusherParams&) -> void;
378379
RC_UE4SS_API auto push_floatproperty(const PusherParams&) -> void;
379380
RC_UE4SS_API auto push_doubleproperty(const PusherParams&) -> void;

UE4SS/src/LuaType/LuaTSet.cpp

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
#include <LuaType/LuaTSet.hpp>
2+
3+
#include <DynamicOutput/DynamicOutput.hpp>
4+
5+
#include <Unreal/Property/FSetProperty.hpp>
6+
7+
namespace RC::LuaType
8+
{
9+
TSet::TSet(const PusherParams& params)
10+
: RemoteObjectBase<Unreal::FScriptSet, TSetName>(static_cast<Unreal::FScriptSet*>(params.data)), m_base(params.base),
11+
m_property(static_cast<Unreal::FSetProperty*>(params.property)), m_element_property(m_property->GetElementProp())
12+
{
13+
}
14+
15+
auto TSet::construct(const PusherParams& params) -> const LuaMadeSimple::Lua::Table
16+
{
17+
LuaType::TSet lua_object{params};
18+
19+
if (!lua_object.m_element_property)
20+
{
21+
Output::send<LogLevel::Error>(STR("TSet::construct: m_element_property is nullptr for {}"), lua_object.m_property->GetFullName());
22+
}
23+
24+
auto metatable_name = ClassName::ToString();
25+
26+
LuaMadeSimple::Lua::Table table = params.lua.get_metatable(metatable_name);
27+
if (params.lua.is_nil(-1))
28+
{
29+
params.lua.discard_value(-1);
30+
LuaMadeSimple::Type::RemoteObject<Unreal::FScriptSet>::construct(params.lua, lua_object);
31+
setup_metamethods(lua_object);
32+
setup_member_functions<LuaMadeSimple::Type::IsFinal::Yes>(table);
33+
params.lua.new_metatable<LuaType::TSet>(metatable_name, lua_object.get_metamethods());
34+
}
35+
36+
// Create object & surrender ownership to lua
37+
params.lua.transfer_stack_object(std::move(lua_object), metatable_name, lua_object.get_metamethods());
38+
39+
return table;
40+
}
41+
42+
auto TSet::construct(const LuaMadeSimple::Lua& lua, BaseObject& construct_to) -> const LuaMadeSimple::Lua::Table
43+
{
44+
LuaMadeSimple::Lua::Table table = LuaMadeSimple::Type::RemoteObject<Unreal::FScriptSet>::construct(lua, construct_to);
45+
46+
setup_member_functions<LuaMadeSimple::Type::IsFinal::No>(table);
47+
48+
setup_metamethods(construct_to);
49+
50+
return table;
51+
}
52+
53+
auto TSet::setup_metamethods(BaseObject& base_object) -> void
54+
{
55+
// Add Length metamethod (# operator in Lua)
56+
base_object.get_metamethods().create(LuaMadeSimple::Lua::MetaMethod::Length,
57+
[](const LuaMadeSimple::Lua& lua) {
58+
auto lua_object = lua.get_userdata<TSet>();
59+
lua.set_integer(lua_object.get_remote_cpp_object()->Num());
60+
return 1;
61+
});
62+
63+
}
64+
65+
template <LuaMadeSimple::Type::IsFinal is_final>
66+
auto TSet::setup_member_functions(const LuaMadeSimple::Lua::Table& table) -> void
67+
{
68+
table.add_pair("IsValid",
69+
[](const LuaMadeSimple::Lua& lua) -> int {
70+
auto& lua_object = lua.get_userdata<TSet>();
71+
72+
lua.set_bool(lua_object.get_remote_cpp_object());
73+
74+
return 1;
75+
});
76+
77+
table.add_pair("Add",
78+
[](const LuaMadeSimple::Lua& lua) -> int {
79+
prepare_to_handle(SetOperation::Add, lua);
80+
return 1;
81+
});
82+
83+
table.add_pair("Contains",
84+
[](const LuaMadeSimple::Lua& lua) -> int {
85+
prepare_to_handle(SetOperation::Contains, lua);
86+
return 1;
87+
});
88+
89+
table.add_pair("Remove",
90+
[](const LuaMadeSimple::Lua& lua) -> int {
91+
prepare_to_handle(SetOperation::Remove, lua);
92+
return 1;
93+
});
94+
95+
table.add_pair("Empty",
96+
[](const LuaMadeSimple::Lua& lua) -> int {
97+
prepare_to_handle(SetOperation::Empty, lua);
98+
return 1;
99+
});
100+
101+
table.add_pair("ForEach",
102+
[](const LuaMadeSimple::Lua& lua) -> int {
103+
prepare_to_handle(SetOperation::ForEach, lua);
104+
return 1;
105+
});
106+
107+
if constexpr (is_final == LuaMadeSimple::Type::IsFinal::Yes)
108+
{
109+
table.add_pair("type",
110+
[](const LuaMadeSimple::Lua& lua) -> int {
111+
lua.set_string(ClassName::ToString());
112+
return 1;
113+
});
114+
}
115+
}
116+
117+
auto TSet::prepare_to_handle(const SetOperation operation, const LuaMadeSimple::Lua& lua) -> void
118+
{
119+
TSet& lua_object = lua.get_userdata<TSet>();
120+
121+
FScriptSetInfo info(lua_object.m_element_property);
122+
info.validate_pushers(lua);
123+
124+
Unreal::FScriptSet* set = lua_object.get_remote_cpp_object();
125+
126+
switch (operation)
127+
{
128+
case SetOperation::Add: {
129+
Unreal::TArray<Unreal::uint8> element_data{};
130+
element_data.Init(0, info.layout.Size);
131+
132+
PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set,
133+
.lua = lua,
134+
.base = lua_object.m_base,
135+
.data = element_data.GetData(),
136+
.property = info.element};
137+
StaticState::m_property_value_pushers[static_cast<int32_t>(info.element_fname.GetComparisonIndex())](pusher_params);
138+
139+
void* element_ptr = element_data.GetData();
140+
141+
auto construct_fn = [&](Unreal::FProperty* property, const void* ptr, void* new_element) {
142+
if (property->HasAnyPropertyFlags(Unreal::EPropertyFlags::CPF_ZeroConstructor))
143+
{
144+
Unreal::FMemory::Memzero(new_element, property->GetSize());
145+
}
146+
else
147+
{
148+
property->InitializeValue(new_element);
149+
}
150+
151+
property->CopySingleValueToScriptVM(new_element, ptr);
152+
};
153+
154+
auto destruct_fn = [&](Unreal::FProperty* property, void* element) {
155+
if (!property->HasAnyPropertyFlags(
156+
Unreal::EPropertyFlags::CPF_IsPlainOldData |
157+
Unreal::EPropertyFlags::CPF_NoDestructor))
158+
{
159+
property->DestroyValue(element);
160+
}
161+
};
162+
163+
set->Add(element_ptr,
164+
info.layout,
165+
[&](const void* src) -> Unreal::uint32 {
166+
return info.element->GetValueTypeHash(src);
167+
},
168+
[&](const void* a, const void* b) -> bool {
169+
return info.element->Identical(a, b);
170+
},
171+
[&](void* new_element) {
172+
construct_fn(info.element, element_ptr, new_element);
173+
},
174+
[&](void* element) {
175+
destruct_fn(info.element, element);
176+
});
177+
break;
178+
}
179+
case SetOperation::Contains: {
180+
Unreal::TArray<Unreal::uint8> element_data{};
181+
element_data.Init(0, info.layout.Size);
182+
183+
PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set,
184+
.lua = lua,
185+
.base = lua_object.m_base,
186+
.data = element_data.GetData(),
187+
.property = info.element};
188+
StaticState::m_property_value_pushers[static_cast<int32_t>(info.element_fname.GetComparisonIndex())](pusher_params);
189+
190+
// Create a SetHelper with the set data
191+
Unreal::FScriptSetHelper SetHelper(lua_object.m_property, set);
192+
193+
// Use FindElementIndex with just the element data
194+
Unreal::int32 index = SetHelper.FindElementIndex(element_data.GetData());
195+
196+
lua.set_bool(index != Unreal::INDEX_NONE);
197+
198+
break;
199+
}
200+
case SetOperation::Remove: {
201+
Unreal::TArray<Unreal::uint8> element_data{};
202+
element_data.Init(0, info.layout.Size);
203+
204+
PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set,
205+
.lua = lua,
206+
.base = lua_object.m_base,
207+
.data = element_data.GetData(),
208+
.property = info.element};
209+
StaticState::m_property_value_pushers[static_cast<int32_t>(info.element_fname.GetComparisonIndex())](pusher_params);
210+
211+
// Create a SetHelper with the set data
212+
Unreal::FScriptSetHelper SetHelper(lua_object.m_property, set);
213+
214+
// Use FindElementIndex with just the element data
215+
Unreal::int32 index = SetHelper.FindElementIndex(element_data.GetData());
216+
217+
if (index != Unreal::INDEX_NONE)
218+
{
219+
SetHelper.RemoveAt(index);
220+
}
221+
222+
break;
223+
}
224+
case SetOperation::Empty: {
225+
set->Empty(0, info.layout);
226+
break;
227+
}
228+
case SetOperation::ForEach: {
229+
Unreal::int32 max_index = set->GetMaxIndex();
230+
for (Unreal::int32 i = 0; i < max_index; i++)
231+
{
232+
if (!set->IsValidIndex(i))
233+
{
234+
continue;
235+
}
236+
237+
// Duplicate the Lua function so we can use it in subsequent iterations
238+
lua_pushvalue(lua.get_lua_state(), 1);
239+
240+
void* element_data = set->GetData(i, info.layout);
241+
242+
// pass element (P1)
243+
PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::GetParam,
244+
.lua = lua,
245+
.base = lua_object.m_base,
246+
.data = element_data,
247+
.property = info.element};
248+
249+
StaticState::m_property_value_pushers[static_cast<int32_t>(info.element_fname.GetComparisonIndex())](pusher_params);
250+
251+
// Call function passing element
252+
lua.call_function(1, 0);
253+
}
254+
break;
255+
}
256+
}
257+
}
258+
259+
FScriptSetInfo::FScriptSetInfo(Unreal::FProperty* element) :
260+
element(element),
261+
element_fname(element->GetClass().GetFName())
262+
{
263+
layout = Unreal::FScriptSet::GetScriptLayout(element->GetSize(),
264+
element->GetMinAlignment());
265+
}
266+
267+
void FScriptSetInfo::validate_pushers(const LuaMadeSimple::Lua& lua)
268+
{
269+
int32_t element_comparison_index = static_cast<int32_t>(element_fname.GetComparisonIndex());
270+
if (!StaticState::m_property_value_pushers.contains(element_comparison_index))
271+
{
272+
std::string element_type_name = to_string(element_fname.ToString());
273+
lua.throw_error(fmt::format("Tried interacting with a set with an unsupported element type {}", element_type_name));
274+
}
275+
}
276+
}

0 commit comments

Comments
 (0)