Skip to content

Commit 8f150bd

Browse files
authored
Merge pull request #1073 from UE4SS-RE/sparsedel
Lua: Delegate support
2 parents 800347c + f2de04a commit 8f150bd

25 files changed

+1323
-20
lines changed

UE4SS/generated_include/MacroSetter.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ if (auto val = parser.get_int64(STR("UFunction"), STR("EventGraphCallOffset"), -
146146
Unreal::UFunction::MemberOffsets.emplace(STR("EventGraphCallOffset"), static_cast<int32_t>(val));
147147
if (auto val = parser.get_int64(STR("UFunction"), STR("Func"), -1); val != -1)
148148
Unreal::UFunction::MemberOffsets.emplace(STR("Func"), static_cast<int32_t>(val));
149+
if (auto val = parser.get_int64(STR("USparseDelegateFunction"), STR("OwningClassName"), -1); val != -1)
150+
Unreal::USparseDelegateFunction::MemberOffsets.emplace(STR("OwningClassName"), static_cast<int32_t>(val));
151+
if (auto val = parser.get_int64(STR("USparseDelegateFunction"), STR("DelegateName"), -1); val != -1)
152+
Unreal::USparseDelegateFunction::MemberOffsets.emplace(STR("DelegateName"), static_cast<int32_t>(val));
149153
if (auto val = parser.get_int64(STR("UField"), STR("Next"), -1); val != -1)
150154
Unreal::UField::MemberOffsets.emplace(STR("Next"), static_cast<int32_t>(val));
151155
if (auto val = parser.get_int64(STR("FField"), STR("ClassPrivate"), -1); val != -1)

UE4SS/include/LuaType/LuaUObject.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ namespace RC::LuaType
388388
RC_UE4SS_API auto push_ansistrproperty(const PusherParams&) -> void;
389389
RC_UE4SS_API auto push_softobjectproperty(const PusherParams&) -> void;
390390
RC_UE4SS_API auto push_interfaceproperty(const PusherParams&) -> void;
391+
RC_UE4SS_API auto push_delegateproperty(const PusherParams&) -> void;
392+
RC_UE4SS_API auto push_multicastdelegateproperty(const PusherParams&) -> void;
393+
RC_UE4SS_API auto push_multicastsparsedelegateproperty(const PusherParams&) -> void;
391394

392395
RC_UE4SS_API auto push_functionproperty(const FunctionPusherParams&) -> void;
393396
// Push to Lua -> END
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#pragma once
2+
3+
#include <LuaType/LuaXProperty.hpp>
4+
5+
namespace RC::Unreal
6+
{
7+
class FDelegateProperty;
8+
class FMulticastDelegateProperty;
9+
class FMulticastSparseDelegateProperty;
10+
}
11+
12+
namespace RC::LuaType
13+
{
14+
struct PusherParams;
15+
16+
// FDelegateProperty (single-cast delegate)
17+
struct FDelegatePropertyName
18+
{
19+
constexpr static const char* ToString()
20+
{
21+
return "DelegateProperty";
22+
}
23+
};
24+
25+
class XDelegateProperty : public RemoteObjectBase<Unreal::FDelegateProperty, FDelegatePropertyName>
26+
{
27+
public:
28+
using Super = XProperty;
29+
30+
private:
31+
explicit XDelegateProperty(Unreal::FDelegateProperty* object);
32+
33+
public:
34+
XDelegateProperty() = delete;
35+
auto static construct(const LuaMadeSimple::Lua&, Unreal::FDelegateProperty*) -> const LuaMadeSimple::Lua::Table;
36+
auto static construct(const LuaMadeSimple::Lua&, BaseObject&) -> const LuaMadeSimple::Lua::Table;
37+
38+
private:
39+
auto static setup_metamethods(BaseObject&) -> void;
40+
41+
private:
42+
template <LuaMadeSimple::Type::IsFinal is_final>
43+
auto static setup_member_functions(const LuaMadeSimple::Lua::Table&) -> void;
44+
};
45+
46+
// FMulticastDelegateProperty (multicast inline delegate)
47+
struct FMulticastDelegatePropertyName
48+
{
49+
constexpr static const char* ToString()
50+
{
51+
return "MulticastDelegateProperty";
52+
}
53+
};
54+
55+
class XMulticastDelegateProperty : public RemoteObjectBase<Unreal::FMulticastDelegateProperty, FMulticastDelegatePropertyName>
56+
{
57+
public:
58+
using Super = XProperty;
59+
60+
private:
61+
Unreal::UObject* m_base{};
62+
Unreal::FMulticastDelegateProperty* m_property{};
63+
64+
private:
65+
explicit XMulticastDelegateProperty(Unreal::FMulticastDelegateProperty* object);
66+
explicit XMulticastDelegateProperty(const PusherParams&);
67+
68+
public:
69+
XMulticastDelegateProperty() = delete;
70+
auto static construct(const PusherParams&) -> const LuaMadeSimple::Lua::Table;
71+
auto static construct(const LuaMadeSimple::Lua&, Unreal::FMulticastDelegateProperty*) -> const LuaMadeSimple::Lua::Table;
72+
auto static construct(const LuaMadeSimple::Lua&, BaseObject&) -> const LuaMadeSimple::Lua::Table;
73+
74+
private:
75+
auto static setup_metamethods(BaseObject&) -> void;
76+
77+
private:
78+
template <LuaMadeSimple::Type::IsFinal is_final>
79+
auto static setup_member_functions(const LuaMadeSimple::Lua::Table&) -> void;
80+
};
81+
82+
// FMulticastSparseDelegateProperty (multicast sparse delegate)
83+
struct FMulticastSparseDelegatePropertyName
84+
{
85+
constexpr static const char* ToString()
86+
{
87+
return "MulticastSparseDelegateProperty";
88+
}
89+
};
90+
91+
class XMulticastSparseDelegateProperty : public RemoteObjectBase<Unreal::FMulticastSparseDelegateProperty, FMulticastSparseDelegatePropertyName>
92+
{
93+
public:
94+
using Super = XProperty;
95+
96+
private:
97+
Unreal::UObject* m_base{};
98+
Unreal::FMulticastSparseDelegateProperty* m_property{};
99+
100+
private:
101+
explicit XMulticastSparseDelegateProperty(Unreal::FMulticastSparseDelegateProperty* object);
102+
explicit XMulticastSparseDelegateProperty(const PusherParams&);
103+
104+
public:
105+
XMulticastSparseDelegateProperty() = delete;
106+
auto static construct(const PusherParams&) -> const LuaMadeSimple::Lua::Table;
107+
auto static construct(const LuaMadeSimple::Lua&, Unreal::FMulticastSparseDelegateProperty*) -> const LuaMadeSimple::Lua::Table;
108+
auto static construct(const LuaMadeSimple::Lua&, BaseObject&) -> const LuaMadeSimple::Lua::Table;
109+
110+
private:
111+
auto static setup_metamethods(BaseObject&) -> void;
112+
113+
private:
114+
template <LuaMadeSimple::Type::IsFinal is_final>
115+
auto static setup_member_functions(const LuaMadeSimple::Lua::Table&) -> void;
116+
};
117+
} // namespace RC::LuaType

UE4SS/src/LuaType/LuaUObject.cpp

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <LuaType/LuaUStruct.hpp>
1919
#include <LuaType/LuaUWorld.hpp>
2020
#include <LuaType/LuaUDataTable.hpp>
21+
#include <LuaType/LuaXDelegateProperty.hpp>
2122
#include <LuaType/LuaXObjectProperty.hpp>
2223
#include <LuaType/LuaXProperty.hpp>
2324
#pragma warning(disable : 4005)
@@ -33,6 +34,9 @@
3334
#include <Unreal/Property/FSoftClassProperty.hpp>
3435
#include <Unreal/Property/FStructProperty.hpp>
3536
#include <Unreal/Property/FWeakObjectProperty.hpp>
37+
#include <Unreal/Property/FDelegateProperty.hpp>
38+
#include <Unreal/Property/FMulticastDelegateProperty.hpp>
39+
#include <Unreal/Property/FMulticastSparseDelegateProperty.hpp>
3640
#include <Unreal/Property/NumericPropertyTypes.hpp>
3741
#include <Unreal/UClass.hpp>
3842
#include <Unreal/UEnum.hpp>
@@ -1822,6 +1826,220 @@ namespace RC::LuaType
18221826
}
18231827
}
18241828

1829+
auto push_delegateproperty(const PusherParams& params) -> void
1830+
{
1831+
using namespace Unreal;
1832+
auto* delegate_value = static_cast<FScriptDelegate*>(params.data);
1833+
if (!delegate_value)
1834+
{
1835+
params.throw_error("push_delegateproperty", "data pointer is nullptr");
1836+
}
1837+
1838+
switch (params.operation)
1839+
{
1840+
case Operation::GetNonTrivialLocal:
1841+
case Operation::Get: {
1842+
// Return a table with {Object = UObject, FunctionName = FName}
1843+
LuaMadeSimple::Lua::Table lua_table = params.lua.prepare_new_table();
1844+
1845+
lua_table.add_key("Object");
1846+
auto_construct_object(params.lua, delegate_value->GetUObject());
1847+
lua_table.fuse_pair();
1848+
1849+
lua_table.add_key("FunctionName");
1850+
FName::construct(params.lua, delegate_value->GetFunctionName());
1851+
lua_table.fuse_pair();
1852+
1853+
lua_table.make_local();
1854+
break;
1855+
}
1856+
case Operation::Set: {
1857+
if (params.lua.is_table())
1858+
{
1859+
lua_pushstring(params.lua.get_lua_state(), "Object");
1860+
int adjusted_index = params.stored_at_index;
1861+
if (params.stored_at_index < 0)
1862+
{
1863+
adjusted_index = params.stored_at_index - 1;
1864+
}
1865+
lua_rawget(params.lua.get_lua_state(), adjusted_index);
1866+
1867+
Unreal::UObject* obj = nullptr;
1868+
if (params.lua.is_userdata())
1869+
{
1870+
const auto& lua_object = params.lua.get_userdata<LuaType::UObject>();
1871+
obj = lua_object.get_remote_cpp_object();
1872+
}
1873+
params.lua.discard_value();
1874+
1875+
lua_pushstring(params.lua.get_lua_state(), "FunctionName");
1876+
lua_rawget(params.lua.get_lua_state(), adjusted_index);
1877+
1878+
Unreal::FName fname = NAME_None;
1879+
if (params.lua.is_userdata())
1880+
{
1881+
auto& lua_fname = params.lua.get_userdata<LuaType::FName>();
1882+
fname = lua_fname.get_local_cpp_object();
1883+
}
1884+
params.lua.discard_value();
1885+
1886+
// Bind the delegate - this modifies the existing FWeakObjectPtr which calls
1887+
// the engine's assignment operator that will allocate serial numbers
1888+
delegate_value->BindUFunction(obj, fname);
1889+
params.lua.discard_value(params.stored_at_index);
1890+
}
1891+
else if (params.lua.is_nil())
1892+
{
1893+
delegate_value->Clear();
1894+
params.lua.discard_value(params.stored_at_index);
1895+
}
1896+
else
1897+
{
1898+
params.throw_error("push_delegateproperty", "Value must be a table {Object = UObject, FunctionName = FName} or nil");
1899+
}
1900+
break;
1901+
}
1902+
case Operation::GetParam:
1903+
RemoteUnrealParam::construct(params.lua, params.data, params.base, params.property);
1904+
break;
1905+
default:
1906+
params.throw_error("push_delegateproperty", "Operation not supported");
1907+
break;
1908+
}
1909+
}
1910+
1911+
auto push_multicastdelegateproperty(const PusherParams& params) -> void
1912+
{
1913+
using namespace Unreal;
1914+
1915+
auto* delegate_property = static_cast<FMulticastDelegateProperty*>(params.property);
1916+
if (!delegate_property)
1917+
{
1918+
params.throw_error("push_multicastdelegateproperty", "property is not FMulticastDelegateProperty");
1919+
}
1920+
1921+
switch (params.operation)
1922+
{
1923+
case Operation::Get:
1924+
XMulticastDelegateProperty::construct(params);
1925+
break;
1926+
case Operation::GetNonTrivialLocal: {
1927+
const FMulticastScriptDelegate* delegate_value = delegate_property->GetMulticastDelegate(params.data);
1928+
if (!delegate_value)
1929+
{
1930+
params.lua.set_nil();
1931+
break;
1932+
}
1933+
1934+
LuaMadeSimple::Lua::Table lua_table = params.lua.prepare_new_table();
1935+
1936+
int32 count = delegate_value->Num();
1937+
for (int32 i = 0; i < count; ++i)
1938+
{
1939+
lua_table.add_key(i + 1); // Lua is 1-indexed
1940+
1941+
LuaMadeSimple::Lua::Table delegate_entry = params.lua.prepare_new_table();
1942+
1943+
delegate_entry.add_key("Object");
1944+
auto_construct_object(params.lua, delegate_value->InvocationList[i].GetUObject());
1945+
delegate_entry.fuse_pair();
1946+
1947+
delegate_entry.add_key("FunctionName");
1948+
FName::construct(params.lua, delegate_value->InvocationList[i].GetFunctionName());
1949+
delegate_entry.fuse_pair();
1950+
1951+
delegate_entry.make_local();
1952+
lua_table.fuse_pair(); // Set array element
1953+
}
1954+
1955+
lua_table.make_local();
1956+
break;
1957+
}
1958+
case Operation::Set:
1959+
// Multicast delegates cannot be set directly. Use the property wrapper methods instead:
1960+
// local prop = obj.OnSomething
1961+
// prop:Add(targetObj, FName("FunctionName"))
1962+
// prop:Remove(targetObj, FName("FunctionName"))
1963+
// prop:Clear()
1964+
params.throw_error("push_multicastdelegateproperty",
1965+
"Multicast delegate values are read-only. Use property methods: GetPropertyByName():Add/Remove/Clear");
1966+
break;
1967+
case Operation::GetParam:
1968+
RemoteUnrealParam::construct(params.lua, params.data, params.base, params.property);
1969+
break;
1970+
default:
1971+
params.throw_error("push_multicastdelegateproperty", "Operation not supported");
1972+
break;
1973+
}
1974+
}
1975+
1976+
auto push_multicastsparsedelegateproperty(const PusherParams& params) -> void
1977+
{
1978+
using namespace Unreal;
1979+
1980+
// Sparse delegates work similarly to regular multicast delegates
1981+
// The main difference is they use 1 byte + global storage instead of inline InvocationList
1982+
auto* delegate_property = static_cast<FMulticastSparseDelegateProperty*>(params.property);
1983+
if (!delegate_property)
1984+
{
1985+
params.throw_error("push_multicastsparsedelegateproperty", "property is not FMulticastSparseDelegateProperty");
1986+
}
1987+
1988+
switch (params.operation)
1989+
{
1990+
case Operation::Get:
1991+
XMulticastSparseDelegateProperty::construct(params);
1992+
break;
1993+
case Operation::GetNonTrivialLocal: {
1994+
const FMulticastScriptDelegate* delegate_value = delegate_property->GetMulticastDelegate(params.data);
1995+
if (!delegate_value)
1996+
{
1997+
params.lua.set_nil();
1998+
break;
1999+
}
2000+
2001+
LuaMadeSimple::Lua::Table lua_table = params.lua.prepare_new_table();
2002+
2003+
int32 count = delegate_value->Num();
2004+
for (int32 i = 0; i < count; ++i)
2005+
{
2006+
lua_table.add_key(i + 1); // Lua is 1-indexed
2007+
2008+
LuaMadeSimple::Lua::Table delegate_entry = params.lua.prepare_new_table();
2009+
2010+
delegate_entry.add_key("Object");
2011+
auto_construct_object(params.lua, delegate_value->InvocationList[i].GetUObject());
2012+
delegate_entry.fuse_pair();
2013+
2014+
delegate_entry.add_key("FunctionName");
2015+
FName::construct(params.lua, delegate_value->InvocationList[i].GetFunctionName());
2016+
delegate_entry.fuse_pair();
2017+
2018+
delegate_entry.make_local();
2019+
lua_table.fuse_pair();
2020+
}
2021+
2022+
lua_table.make_local();
2023+
break;
2024+
}
2025+
case Operation::Set:
2026+
// Multicast delegates cannot be set directly. Use the property wrapper methods instead:
2027+
// local prop = obj.OnSomething
2028+
// prop:Add(targetObj, FName("FunctionName"))
2029+
// prop:Remove(targetObj, FName("FunctionName"))
2030+
// prop:Clear()
2031+
params.throw_error("push_multicastsparsedelegateproperty",
2032+
"Sparse multicast delegate values are read-only. Use property methods: GetPropertyByName():Add/Remove/Clear");
2033+
break;
2034+
case Operation::GetParam:
2035+
RemoteUnrealParam::construct(params.lua, params.data, params.base, params.property);
2036+
break;
2037+
default:
2038+
params.throw_error("push_multicastsparsedelegateproperty", "Operation not supported");
2039+
break;
2040+
}
2041+
}
2042+
18252043
auto static is_a_internal(const LuaMadeSimple::Lua& lua, Unreal::UObject* object, Unreal::UClass* object_class) -> bool
18262044
{
18272045
if (!object || !object_class)

0 commit comments

Comments
 (0)