diff --git a/UE4SS/include/LuaType/LuaTMap.hpp b/UE4SS/include/LuaType/LuaTMap.hpp new file mode 100644 index 000000000..8258f4f58 --- /dev/null +++ b/UE4SS/include/LuaType/LuaTMap.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include + +namespace RC::Unreal +{ + +} + +namespace RC::LuaType +{ + struct TMapName + { + constexpr static const char* ToString() + { + return "TMap"; + } + }; + + class TMap : public RemoteObjectBase + { + private: + Unreal::UObject* m_base; + + Unreal::FMapProperty* m_property; + Unreal::FProperty* m_key_property; + Unreal::FProperty* m_value_property; + + private: + explicit TMap(const PusherParams&); + + public: + TMap() = delete; + + auto static construct(const PusherParams&) -> const LuaMadeSimple::Lua::Table; + auto static construct(const LuaMadeSimple::Lua&, BaseObject&) -> const LuaMadeSimple::Lua::Table; + + private: + auto static setup_metamethods(BaseObject&) -> void; + + private: + template + auto static setup_member_functions(const LuaMadeSimple::Lua::Table&) -> void; + + enum class MapOperation + { + Find, + Add, + Contains, + Remove, + Empty, + }; + + auto static prepare_to_handle(MapOperation, const LuaMadeSimple::Lua&) -> void; + }; + + struct FScriptMapInfo + { + Unreal::FProperty* key{}; + Unreal::FProperty* value{}; + + Unreal::FName key_fname{}; + Unreal::FName value_fname{}; + + Unreal::FScriptMapLayout layout{}; + + FScriptMapInfo(Unreal::FProperty* key, Unreal::FProperty* value); + + /** + * Validates existence of lua pushers for this key/values in this structure. + * Throws if a pusher for a key/value was not found + * + * @param lua Lua state to throw against. + */ + void validate_pushers(const LuaMadeSimple::Lua& lua); + }; +} \ No newline at end of file diff --git a/UE4SS/include/LuaType/LuaUObject.hpp b/UE4SS/include/LuaType/LuaUObject.hpp index 29591a1ca..6b55d853b 100644 --- a/UE4SS/include/LuaType/LuaUObject.hpp +++ b/UE4SS/include/LuaType/LuaUObject.hpp @@ -363,6 +363,7 @@ namespace RC::LuaType RC_UE4SS_API auto push_uint64property(const PusherParams&) -> void; RC_UE4SS_API auto push_structproperty(const PusherParams&) -> void; RC_UE4SS_API auto push_arrayproperty(const PusherParams&) -> void; + RC_UE4SS_API auto push_mapproperty(const PusherParams&) -> void; RC_UE4SS_API auto push_floatproperty(const PusherParams&) -> void; RC_UE4SS_API auto push_doubleproperty(const PusherParams&) -> void; RC_UE4SS_API auto push_boolproperty(const PusherParams&) -> void; diff --git a/UE4SS/src/LuaType/LuaTMap.cpp b/UE4SS/src/LuaType/LuaTMap.cpp new file mode 100644 index 000000000..47c0596d6 --- /dev/null +++ b/UE4SS/src/LuaType/LuaTMap.cpp @@ -0,0 +1,370 @@ +#include + +#include + +#include + + +namespace RC::LuaType +{ + TMap::TMap(const PusherParams& params) + : RemoteObjectBase(static_cast(params.data)), m_base(params.base), + m_property(static_cast(params.property)), + m_key_property(m_property->GetKeyProp()), m_value_property(m_property->GetValueProp()) + { + } + + auto TMap::construct(const PusherParams& params) -> const LuaMadeSimple::Lua::Table + { + LuaType::TMap lua_object{params}; + + if (!lua_object.m_key_property) + { + Output::send(STR("TMap::construct: m_key_property is nullptr for {}"), lua_object.m_property->GetFullName()); + } + + if (!lua_object.m_value_property) + { + Output::send(STR("TMap::construct: m_value_property is nullptr for {}"), lua_object.m_property->GetFullName()); + } + + auto metatable_name = ClassName::ToString(); + + LuaMadeSimple::Lua::Table table = params.lua.get_metatable(metatable_name); + if (params.lua.is_nil(-1)) + { + params.lua.discard_value(-1); + LuaMadeSimple::Type::RemoteObject::construct(params.lua, lua_object); + setup_metamethods(lua_object); + setup_member_functions(table); + params.lua.new_metatable(metatable_name, lua_object.get_metamethods()); + } + + // Create object & surrender ownership to lua + params.lua.transfer_stack_object(std::move(lua_object), metatable_name, lua_object.get_metamethods()); + + return table; + } + + auto TMap::construct(const LuaMadeSimple::Lua& lua, BaseObject& construct_to) -> const LuaMadeSimple::Lua::Table + { + LuaMadeSimple::Lua::Table table = LuaMadeSimple::Type::RemoteObject::construct(lua, construct_to); + + setup_member_functions(table); + + setup_metamethods(construct_to); + + return table; + } + + auto TMap::setup_metamethods(BaseObject& base_object) -> void + { + base_object.get_metamethods().create(LuaMadeSimple::Lua::MetaMethod::Length, + [](const LuaMadeSimple::Lua& lua) { + auto lua_object = lua.get_userdata(); + lua.set_integer(lua_object.get_remote_cpp_object()->Num()); + return 1; + }); + } + + + template + auto TMap::setup_member_functions(const LuaMadeSimple::Lua::Table& table) -> void + { + table.add_pair("IsValid", + [](const LuaMadeSimple::Lua& lua) -> int { + auto& lua_object = lua.get_userdata(); + + lua.set_bool(lua_object.get_remote_cpp_object()); + + return 1; + }); + + table.add_pair("Find", + [](const LuaMadeSimple::Lua& lua) -> int { + prepare_to_handle(MapOperation::Find, lua); + return 1; + }); + + table.add_pair("Add", + [](const LuaMadeSimple::Lua& lua) -> int { + prepare_to_handle(MapOperation::Add, lua); + return 1; + }); + + table.add_pair("Contains", + [](const LuaMadeSimple::Lua& lua) -> int { + prepare_to_handle(MapOperation::Contains, lua); + return 1; + }); + + table.add_pair("Remove", + [](const LuaMadeSimple::Lua& lua) -> int { + prepare_to_handle(MapOperation::Remove, lua); + return 1; + }); + + table.add_pair("Empty", + [](const LuaMadeSimple::Lua& lua) -> int { + prepare_to_handle(MapOperation::Empty, lua); + return 1; + }); + + table.add_pair("ForEach", + [](const LuaMadeSimple::Lua& lua) -> int { + TMap& lua_object = lua.get_userdata(); + + FScriptMapInfo info(lua_object.m_key_property, lua_object.m_value_property); + info.validate_pushers(lua); + + Unreal::FScriptMap* map = lua_object.get_remote_cpp_object(); + + Unreal::int32 max_index = map->GetMaxIndex(); + for (Unreal::int32 i = 0; i < max_index; i++) + { + if (!map->IsValidIndex(i)) + { + continue; + } + + // Duplicate the Lua function so that we can use it in subsequent iterations of this loop (call_function pops the function from the stack) + lua_pushvalue(lua.get_lua_state(), 1); + + void* entry_data = map->GetData(i, info.layout); + + // pass key (P1), value (P2) + PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::GetParam, + .lua = lua, + .base = lua_object.m_base, + .data = entry_data, + .property = nullptr}; + + pusher_params.property = info.key; + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + + pusher_params.data = static_cast(pusher_params.data) + info.layout.ValueOffset; + pusher_params.property = info.value; + StaticState::m_property_value_pushers[static_cast(info.value_fname.GetComparisonIndex())](pusher_params); + + // Call function passing key & value + // Mutating the key is undefined behavior + lua.call_function(2, 0); + } + + return 1; + }); + + if constexpr (is_final == LuaMadeSimple::Type::IsFinal::Yes) + { + table.add_pair("type", + [](const LuaMadeSimple::Lua& lua) -> int { + lua.set_string(ClassName::ToString()); + return 1; + }); + + // If this is the final object then we also want to finalize creating the table + // If not then it's the responsibility of the overriding object to call 'make_global()' + // table.make_global(ClassName::ToString()); + } + } + + auto TMap::prepare_to_handle(const MapOperation operation, const LuaMadeSimple::Lua& lua) -> void + { + TMap& lua_object = lua.get_userdata(); + + FScriptMapInfo info(lua_object.m_key_property, lua_object.m_value_property); + info.validate_pushers(lua); + + Unreal::FScriptMap* map = lua_object.get_remote_cpp_object(); + + switch (operation) + { + case MapOperation::Find: { + Unreal::TArray key_data{}; + key_data.Init(0, info.layout.ValueOffset); + + { + const PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set, + .lua = lua, + .base = lua_object.m_base, + .data = key_data.GetData(), + .property = info.key}; + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + } + + Unreal::uint8* value_ptr = map->FindValue(key_data.GetData(), + info.layout, + [&](const void* src) -> Unreal::uint32 { + return info.key->GetValueTypeHash(src); + }, + [&](const void* a, const void* b) -> bool { + return info.key->Identical(a, b); + }); + if (!value_ptr) + { + lua.throw_error("Map key not found."); + } + + const PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::GetParam, + .lua = lua, + .base = lua_object.m_base, + .data = value_ptr, + .property = info.value}; + StaticState::m_property_value_pushers[static_cast(info.value_fname.GetComparisonIndex())](pusher_params); + break; + } + case MapOperation::Add: { + Unreal::TArray pair_data{}; + pair_data.Init(0, info.layout.SetLayout.Size); + + PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set, + .lua = lua, + .base = lua_object.m_base, + .data = pair_data.GetData(), + .property = info.key}; + + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + + pusher_params.property = info.value; + pusher_params.data = static_cast(pusher_params.data) + info.layout.ValueOffset; + StaticState::m_property_value_pushers[static_cast(info.value_fname.GetComparisonIndex())](pusher_params); + + void* key_ptr = pair_data.GetData(); + void* value_ptr = pair_data.GetData() + info.layout.ValueOffset; + + auto construct_fn = [&](Unreal::FProperty* property, const void* ptr, void* new_element) { + if (property->HasAnyPropertyFlags(Unreal::EPropertyFlags::CPF_ZeroConstructor)) + { + Unreal::FMemory::Memzero(new_element, property->GetSize()); + } + else + { + property->InitializeValue(new_element); + } + + property->CopySingleValueToScriptVM(new_element, ptr); + }; + + auto destruct_fn = [&](Unreal::FProperty* property, void* element) { + if (!property->HasAnyPropertyFlags( + Unreal::EPropertyFlags::CPF_IsPlainOldData | + Unreal::EPropertyFlags::CPF_NoDestructor)) + { + property->DestroyValue(element); + } + }; + + map->Add(key_ptr, + value_ptr, + info.layout, + [&](const void* src) -> Unreal::uint32 { + return info.key->GetValueTypeHash(src); + }, + [&](const void* a, const void* b) -> bool { + return info.key->Identical(a, b); + }, + [&](void* new_element_key) { + construct_fn(info.key, key_ptr, new_element_key); + }, + [&](void* new_element_value) { + construct_fn(info.value, value_ptr, new_element_value); + }, + [&](void* existing_element_value) { + info.value->CopySingleValueToScriptVM(existing_element_value, value_ptr); + }, + [&](void* element_key) { + destruct_fn(info.key, element_key); + }, + [&](void* element_value) { + destruct_fn(info.value, element_value); + }); + break; + } + case MapOperation::Contains: { + Unreal::TArray key_data{}; + key_data.Init(0, info.layout.ValueOffset); + + PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set, + .lua = lua, + .base = lua_object.m_base, + .data = key_data.GetData(), + .property = info.key}; + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + + Unreal::int32 index = map->FindPairIndex(key_data.GetData(), + info.layout, + [&](const void* src) { + return info.key->GetValueTypeHash(src); + }, + [&](const void* a, const void* b) { + return info.key->Identical(a, b); + }); + + lua.set_bool(index != INDEX_NONE); + + break; + } + case MapOperation::Remove: { + Unreal::TArray key_data{}; + key_data.Init(0, info.layout.ValueOffset); + + PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::Set, + .lua = lua, + .base = lua_object.m_base, + .data = key_data.GetData(), + .property = info.key}; + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + + Unreal::int32 index = map->FindPairIndex(key_data.GetData(), + info.layout, + [&](const void* src) { + return info.key->GetValueTypeHash(src); + }, + [&](const void* a, const void* b) { + return info.key->Identical(a, b); + }); + + if (index != INDEX_NONE) + { + map->RemoveAt(index, info.layout); + } + + break; + } + case MapOperation::Empty: { + map->Empty(0, info.layout); + break; + } + } + } + + FScriptMapInfo::FScriptMapInfo(Unreal::FProperty* key, Unreal::FProperty* value) : + key(key), value(value), + key_fname(key->GetClass().GetFName()), value_fname(value->GetClass().GetFName()) + { + layout = Unreal::FScriptMap::GetScriptLayout(key->GetSize(), + key->GetMinAlignment(), + value->GetSize(), + value->GetMinAlignment()); + + } + + void FScriptMapInfo::validate_pushers(const LuaMadeSimple::Lua& lua) + { + int32_t key_comparison_index = static_cast(key_fname.GetComparisonIndex()); + if (!StaticState::m_property_value_pushers.contains(key_comparison_index)) + { + std::string inner_type_name = to_string(key_fname.ToString()); + lua.throw_error(fmt::format("Tried interacting with a map with an unsupported key type {}", inner_type_name)); + } + + int32_t value_comparison_index = static_cast(value_fname.GetComparisonIndex()); + if (!StaticState::m_property_value_pushers.contains(value_comparison_index)) + { + std::string inner_type_name = to_string(key_fname.ToString()); + lua.throw_error(fmt::format("Tried interacting with a map with an unsupported value type {}", inner_type_name)); + } + } + + +} \ No newline at end of file diff --git a/UE4SS/src/LuaType/LuaUObject.cpp b/UE4SS/src/LuaType/LuaUObject.cpp index 25a0372cc..0ba377ba9 100644 --- a/UE4SS/src/LuaType/LuaUObject.cpp +++ b/UE4SS/src/LuaType/LuaUObject.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -851,6 +853,120 @@ namespace RC::LuaType params.throw_error("push_arrayproperty", "Operation not supported"); } + auto push_mapproperty(const PusherParams& params) -> void + { + auto map_to_lua_table = [&](const LuaMadeSimple::Lua& lua, Unreal::FProperty* property, void* data_ptr) { + Unreal::FMapProperty* map_property = static_cast(property); + + FScriptMapInfo info(map_property->GetKeyProp(), map_property->GetValueProp()); + info.validate_pushers(lua); + + Unreal::FScriptMap* map = static_cast(data_ptr); + + LuaMadeSimple::Lua::Table lua_table = [&]() { + if (params.create_new_if_get_non_trivial_local) + { + return lua.prepare_new_table(); + } + else + { + return lua.get_table(); + } + }(); + + Unreal::int32 max_index = map->GetMaxIndex(); + for (Unreal::int32 i = 0; i < max_index; i++) + { + if (!map->IsValidIndex(i)) + { + continue; + } + + PusherParams pusher_params{.operation = LuaMadeSimple::Type::Operation::GetParam, + .lua = lua, + .base = params.base, + .data = static_cast(map->GetData(i, info.layout)), + .property = nullptr}; + + pusher_params.property = info.key; + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + + pusher_params.data = static_cast(pusher_params.data) + info.layout.ValueOffset; + pusher_params.property = info.value; + StaticState::m_property_value_pushers[static_cast(info.value_fname.GetComparisonIndex())](pusher_params); + + lua_table.fuse_pair(); + } + + lua_table.make_local(); + }; + + auto lua_table_to_map = [&]() { + Unreal::FMapProperty* map_property = static_cast(params.property); + + FScriptMapInfo info(map_property->GetKeyProp(), map_property->GetValueProp()); + info.validate_pushers(params.lua); + + auto map = new(params.data) Unreal::FScriptMap{}; + + params.lua.for_each_in_table([&](LuaMadeSimple::LuaTableReference table) -> bool { + params.lua.insert_value(-2); + params.lua.insert_value(-1); + + // be careful with this function, if you add a duplicate entry, + // rehashing the map will NOT remove it + // if you want identical behavior to TMap::Add use the Add function. + Unreal::int32 added_index = map->AddUninitialized(info.layout); + + PusherParams pusher_params{.operation = Operation::Set, + .lua = params.lua, + .base = static_cast(map->GetData(0, info.layout)), + .data = map->GetData(added_index, info.layout), + .property = nullptr, + .stored_at_index = 1}; + + Unreal::FMemory::Memzero(pusher_params.data, info.layout.SetLayout.Size); + + pusher_params.property = info.key; + StaticState::m_property_value_pushers[static_cast(info.key_fname.GetComparisonIndex())](pusher_params); + + pusher_params.data = static_cast(pusher_params.data) + info.layout.ValueOffset; + pusher_params.property = info.value; + pusher_params.stored_at_index = 2; + StaticState::m_property_value_pushers[static_cast(info.value_fname.GetComparisonIndex())](pusher_params); + + return false; + }); + + map->Rehash(info.layout, + [&](const void* src) -> Unreal::uint32 { + return info.key->GetValueTypeHash(src); + }); + }; + + switch (params.operation) + { + case Operation::Get: + TMap::construct(params); + return; + case Operation::GetNonTrivialLocal: + map_to_lua_table(params.lua, params.property, params.data); + return; + case Operation::Set: + lua_table_to_map(); + return; + case Operation::GetParam: + RemoteUnrealParam::construct(params.lua, params.data, params.base, params.property); + return; + default: + params.throw_error("push_mapproperty", "Unhandled Operation"); + break; + } + + params.throw_error("push_mapproperty", fmt::format("Unknown Operation ({}) not supported", static_cast(params.operation))); + } + + auto push_functionproperty(const FunctionPusherParams& params) -> void { UFunction::construct(params.lua, params.base, params.function); diff --git a/UE4SS/src/UE4SSProgram.cpp b/UE4SS/src/UE4SSProgram.cpp index 63f88ca9c..e13776b4f 100644 --- a/UE4SS/src/UE4SSProgram.cpp +++ b/UE4SS/src/UE4SSProgram.cpp @@ -983,6 +983,7 @@ namespace RC LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("UInt64Property")).GetComparisonIndex(), &LuaType::push_uint64property); LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("StructProperty")).GetComparisonIndex(), &LuaType::push_structproperty); LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("ArrayProperty")).GetComparisonIndex(), &LuaType::push_arrayproperty); + LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("MapProperty")).GetComparisonIndex(), &LuaType::push_mapproperty); LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("FloatProperty")).GetComparisonIndex(), &LuaType::push_floatproperty); LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("DoubleProperty")).GetComparisonIndex(), &LuaType::push_doubleproperty); LuaType::StaticState::m_property_value_pushers.emplace(FName(STR("BoolProperty")).GetComparisonIndex(), &LuaType::push_boolproperty); diff --git a/assets/Changelog.md b/assets/Changelog.md index 29c119793..b2da9d40c 100644 --- a/assets/Changelog.md +++ b/assets/Changelog.md @@ -43,6 +43,8 @@ Added search filter: `IncludeClassNames`. ([UE4SS #472](https://github.com/UE4SS ### Lua API +Added `TMap` implementation. [UE4SS #755](https://github.com/UE4SS-RE/RE-UE4SS/issues/755) + Added global function `CreateInvalidObject`, which returns an invalid UObject. ([UE4SS #652](https://github.com/UE4SS-RE/RE-UE4SS/issues/652)) Added GenerateLuaTypes function. ([UE4SS #664](https://github.com/UE4SS-RE/RE-UE4SS/pull/664)) @@ -53,6 +55,7 @@ Added global Dumpers functions to Types.lua. ([UE4SS #664](https://github.com/UE - Added `NAME_None` definition - Added `EFindName` enum definition - Added `FName` function overloads with FindType parameter +- Added `TMap` definitions #### UEHelpers - Added function `GetPlayer` which is just a fast way to get player controlled Pawn (the majority of the time it will be the player character) [PR #650](https://github.com/UE4SS-RE/RE-UE4SS/pull/650) diff --git a/assets/Mods/shared/Types.lua b/assets/Mods/shared/Types.lua index fcadb895b..2b95ca9d2 100644 --- a/assets/Mods/shared/Types.lua +++ b/assets/Mods/shared/Types.lua @@ -963,6 +963,45 @@ function TArray:ForEach(Callback) end ---@class TSet : { [K]: nil } ---@class TMap : { [K]: V } +TMap = {} + +---Find the specified key in the map +---Throws an exception if the key is not found +---@generic K +---@generic V +---@param key K +---@return V +function TMap:Find(key) end + +---Inserts a key/value pair into the map +---If the key already exists in the map, replaces the value +---@generic K +---@generic V +---@param key K +---@param value V +function TMap:Add(key, value) end + +---Checks if a key exists inside of the map +---@generic K +---@param key K +---@return boolean +function TMap:Contains(key) end + +---Removes an element from the map +---If the element doesn't exist, does nothing +---@generic K +---@param key K +function TMap:Remove(key) end + +---Clears the map +function TMap:Empty() end + +--- Iterates the entire `TMap` and calls the callback function for each element in the array +--- The callback params are: `RemoteUnrealParam key`, `RemoteUnrealParam value` | `LocalUnrealParam value` +--- Use `elem:get()` and `elem:set()` to access/mutate the value +--- Mutating the key is undefined behavior +--- @param callback fun(key: RemoteUnrealParam, value: RemoteUnrealParam) +function TMap:ForEach(callback) end ---@class TScriptInterface diff --git a/deps/first/Unreal b/deps/first/Unreal index 9ff02d739..5364faf6a 160000 --- a/deps/first/Unreal +++ b/deps/first/Unreal @@ -1 +1 @@ -Subproject commit 9ff02d7391f045a8f0491b3187802d4a0d597242 +Subproject commit 5364faf6a2a0858f7b7b8520ec6867018cbfbb11 diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index b344b3d7b..df53ee7df 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -43,6 +43,7 @@ - [FText](./lua-api/classes/ftext.md) - [FieldClass](./lua-api/classes/fieldclass.md) - [TArray](./lua-api/classes/tarray.md) + - [TMap](./lua-api/classes/tmap.md) - [RemoteUnrealParam](./lua-api/classes/remoteunrealparam.md) - [LocalUnrealParam](./lua-api/classes/localunrealparam.md) - [Property](./lua-api/classes/property.md) diff --git a/docs/lua-api/classes/tmap.md b/docs/lua-api/classes/tmap.md new file mode 100644 index 000000000..b50a8820f --- /dev/null +++ b/docs/lua-api/classes/tmap.md @@ -0,0 +1,39 @@ +# TMap + +## Inheritance +[RemoteObject](./remoteobject.md) + +## Metamethods + +### __len +- **Usage:** `#TMap` +- **Return type:** `integer` +- Returns the number of current pairs in the map. + +## Methods + +### Find(key) +- **Return type:** `RemoteUnrealParam` | `LocalUnrealParam` +- **Returns:** the element found in the map +- **Throws:** if an element was not found in the map +- Finds the specified key in the map + +### Add(key, value) +- Inserts a key/value pair into the map. If the key already exists in the map, replaces the value. + +### Contains(key) +- **Return type:** `bool` +- **Returns:** if the element exists in the map. +- Checks if a key exists inside of the map. + +### Remove(key) +- Removes an element from the map. If an element doesn't exist, does nothing. + +### Empty() +- Clears the map. + +### ForEach(function Callback) +- Iterates the entire `TMap` and calls the callback function for each element in the array. +- The callback params are: `RemoteUnrealParam key`, `RemoteUnrealParam value` | `LocalUnrealParam value`. +- Use `elem:get()` and `elem:set()` to access/mutate the value. +- Mutating the key is undefined behavior.