From ff4046b866f7482cc0f3831f52b43f40f133ad6f Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sat, 24 Aug 2024 19:19:38 +0500 Subject: [PATCH 1/2] wip --- .../CustomPropertyBinding.cpp | 57 ++++++++++++++++--- .../cpp/server_guest_lib/DynamicFields.cpp | 21 ++++--- .../cpp/server_guest_lib/DynamicFields.h | 26 ++++++++- .../server_guest_lib/MpObjectReference.cpp | 10 ++-- .../cpp/server_guest_lib/MpObjectReference.h | 5 +- 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp b/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp index 86bb0ef192..579a3b6336 100644 --- a/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp +++ b/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp @@ -42,14 +42,27 @@ Napi::Value CustomPropertyBinding::Get(Napi::Env env, ScampServer& scampServer, auto& refr = partOne->worldState.GetFormAt(formId); - if (isPrivate) { - return NapiHelper::ParseJson(env, - refr.GetDynamicFields().Get(propertyName)); - } else { + if (!isPrivate) { EnsurePropertyExists(scampServer.GetGamemodeApiState(), propertyName); - return NapiHelper::ParseJson(env, - refr.GetDynamicFields().Get(propertyName)); } + + auto& fynamicFields = refr.GetDynamicFields(); + auto& entry = dynamicFields.Get(propertyName); + + if (entry.cache != std::nullopt) { + switch (entry.cache->index()) { + case static_cast(DynamicFieldsEntryCacheIndex::kVoidPtr): + return static_cast(std::get(*entry.cache)); + case static_cast(DynamicFieldsEntryCacheIndex::kDouble): + return Napi::Number::New(env, std::get(*entry.cache)); + case static_cast(DynamicFieldsEntryCacheIndex::kBool): + return Napi::Boolean::New(env, std::get(*entry.cache)); + case static_cast(DynamicFieldsEntryCacheIndex::kString): + return Napi::String::New(env, std::get(*entry.cache)); + } + } + + return NapiHelper::ParseJson(env, entry.value); } void CustomPropertyBinding::Set(Napi::Env env, ScampServer& scampServer, @@ -64,14 +77,42 @@ void CustomPropertyBinding::Set(Napi::Env env, ScampServer& scampServer, auto newValueDump = NapiHelper::Stringify(env, newValue); auto newValueJson = nlohmann::json::parse(newValueDump); + std::optional newValueCache; + + switch (newValue.Type()) { + case napi_undefined: + break; + case napi_null: + newValueCache = static_cast(nullptr); + break; + case napi_boolean: + newValueCache = newValue.As().Value(); + break; + case napi_number: + break; + case napi_string: + break; + case napi_symbol: + break; + case napi_object: + break; + case napi_function: + break; + case napi_external: + break; + case napi_bigint: + break; + } + if (isPrivate) { - refr.SetProperty(propertyName, newValueJson, false, false); + refr.SetProperty(propertyName, newValueJson, newValueCache, false, false); if (isPrivateIndexed) { refr.RegisterPrivateIndexedProperty(propertyName, newValueDump); } return; } auto it = EnsurePropertyExists(state, propertyName); - refr.SetProperty(propertyName, newValueJson, it->second.isVisibleByOwner, + refr.SetProperty(propertyName, newValueJson, newValueCache, + it->second.isVisibleByOwner, it->second.isVisibleByNeighbors); } diff --git a/skymp5-server/cpp/server_guest_lib/DynamicFields.cpp b/skymp5-server/cpp/server_guest_lib/DynamicFields.cpp index 73882ae629..c19e27a2dc 100644 --- a/skymp5-server/cpp/server_guest_lib/DynamicFields.cpp +++ b/skymp5-server/cpp/server_guest_lib/DynamicFields.cpp @@ -3,19 +3,20 @@ #include void DynamicFields::Set(const std::string& propName, - const nlohmann::json& value) + const DynamicFieldsEntryValue& value) { jsonCache.reset(); props[propName] = value; } -const nlohmann::json& DynamicFields::Get(const std::string& propName) const +const DynamicFieldsEntryValue& DynamicFields::Get( + const std::string& propName) const { - static const auto kNull = nlohmann::json(); + static const DynamicFieldsEntryValue kNullEntry = DynamicFieldsValueObject(); auto it = props.find(propName); if (it == props.end()) { - return kNull; + return kNullEntry; } return it->second; } @@ -27,7 +28,7 @@ const nlohmann::json& DynamicFields::GetAsJson() const auto obj = nlohmann::json::object(); for (auto& [key, v] : props) { - obj[key] = v; + obj[key] = DynamicFields::ToJson(v); } jsonCache = std::move(obj); @@ -38,9 +39,9 @@ const nlohmann::json& DynamicFields::GetAsJson() const DynamicFields DynamicFields::FromJson(const nlohmann::json& j) { DynamicFields res; - for (auto it = j.begin(); it != j.end(); ++it) { - res.props[it.key()] = it.value(); - } + // for (auto it = j.begin(); it != j.end(); ++it) { + // res.props[it.key()] = it.value(); + // } return res; } @@ -58,3 +59,7 @@ bool operator!=(const DynamicFields& r, const DynamicFields& l) { return !(r == l); } + +nlohmann::json DynamicFields::ToJson(const DynamicFieldsEntryValue& value) +{ +} \ No newline at end of file diff --git a/skymp5-server/cpp/server_guest_lib/DynamicFields.h b/skymp5-server/cpp/server_guest_lib/DynamicFields.h index 8a3b578879..cc4693fe1a 100644 --- a/skymp5-server/cpp/server_guest_lib/DynamicFields.h +++ b/skymp5-server/cpp/server_guest_lib/DynamicFields.h @@ -4,12 +4,30 @@ #include #include #include +#include + +enum class DynamicFieldsEntryCacheIndex +{ + kObject, + kDouble, + kBool, + kString +}; + +struct DynamicFieldsValueObject +{ + void* napiValue = nullptr; + std::string jsonDump; +}; + +using DynamicFieldsEntryValue = + std::variant value; class DynamicFields { public: - void Set(const std::string& propName, const nlohmann::json& value); - const nlohmann::json& Get(const std::string& propName) const; + void Set(const std::string& propName, const DynamicFieldsEntryValue& value); + const DynamicFieldsEntryValue& Get(const std::string& propName) const; const nlohmann::json& GetAsJson() const; static DynamicFields FromJson(const nlohmann::json& j); @@ -27,6 +45,8 @@ class DynamicFields friend bool operator!=(const DynamicFields& r, const DynamicFields& l); private: - std::unordered_map props; + static nlohmann::json ToJson(const DynamicFieldsEntryValue& value); + + std::unordered_map props; mutable std::optional jsonCache; }; diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 31e61275e7..741fec55fb 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -684,13 +684,13 @@ void MpObjectReference::UpdateHoster(uint32_t newHosterId) } } -void MpObjectReference::SetProperty(const std::string& propertyName, - const nlohmann::json& newValue, - bool isVisibleByOwner, - bool isVisibleByNeighbor) +void MpObjectReference::SetProperty( + const std::string& propertyName, const nlohmann::json& newValue, + std::optional cache, bool isVisibleByOwner, + bool isVisibleByNeighbor) { EditChangeForm([&](MpChangeFormREFR& changeForm) { - changeForm.dynamicFields.Set(propertyName, newValue); + changeForm.dynamicFields.Set(propertyName, newValue, cache); }); if (isVisibleByNeighbor) { SendPropertyToListeners(propertyName.data(), newValue); diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h index f8904463bb..06052de0f6 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h @@ -136,8 +136,9 @@ class MpObjectReference void SetPrimitive(const NiPoint3& boundsDiv2); void UpdateHoster(uint32_t newHosterId); void SetProperty(const std::string& propertyName, - const nlohmann::json& newValue, bool isVisibleByOwner, - bool isVisibleByNeighbor); + const nlohmann::json& newValue, + std::optional cache, + bool isVisibleByOwner, bool isVisibleByNeighbor); void SetTeleportFlag(bool value); void SetPosAndAngleSilent(const NiPoint3& pos, const NiPoint3& rot); void Delete(); From 60e0f4e710a55978c872f9a0c174a93fda024156 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Wed, 28 Aug 2024 00:25:07 +0500 Subject: [PATCH 2/2] . --- skymp5-server/cpp/addon/ScampServer.cpp | 7 ++ skymp5-server/cpp/addon/ScampServer.h | 2 +- .../CustomPropertyBinding.cpp | 94 ++++++++++++++++++- .../cpp/server_guest_lib/DynamicFields.h | 1 + 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/skymp5-server/cpp/addon/ScampServer.cpp b/skymp5-server/cpp/addon/ScampServer.cpp index d314714acb..2e74aeefdf 100644 --- a/skymp5-server/cpp/addon/ScampServer.cpp +++ b/skymp5-server/cpp/addon/ScampServer.cpp @@ -357,6 +357,13 @@ ScampServer::ScampServer(const Napi::CallbackInfo& info) emitter = Napi::Persistent(res.As()); emit = Napi::Persistent(emitter.Value().Get("emit").As()); + + auto structuredCloneValue = env.Gloval().Get("structuredClone"); + if (structuredCloneValue.IsUndefined()) { + throw std::runtime_error("structuredClone is not defined"); + } + structuredClone = + Napi::Persistent(structuredCloneValue.As()); } catch (std::exception& e) { throw Napi::Error::New(info.Env(), (std::string)e.what()); } diff --git a/skymp5-server/cpp/addon/ScampServer.h b/skymp5-server/cpp/addon/ScampServer.h index 796446e65a..0c13324713 100644 --- a/skymp5-server/cpp/addon/ScampServer.h +++ b/skymp5-server/cpp/addon/ScampServer.h @@ -79,7 +79,7 @@ class ScampServer : public Napi::ObjectWrap Napi::Env tickEnv; Napi::ObjectReference emitter; Napi::ObjectReference self; - Napi::FunctionReference emit; + Napi::FunctionReference emit, structuredClone; std::shared_ptr logger; nlohmann::json serverSettings; GamemodeApi::State gamemodeApiState; diff --git a/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp b/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp index 579a3b6336..40c3b54f51 100644 --- a/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp +++ b/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp @@ -52,13 +52,27 @@ Napi::Value CustomPropertyBinding::Get(Napi::Env env, ScampServer& scampServer, if (entry.cache != std::nullopt) { switch (entry.cache->index()) { case static_cast(DynamicFieldsEntryCacheIndex::kVoidPtr): - return static_cast(std::get(*entry.cache)); + +// TODO: !!!!!!!!!!!!!!!!!! Bad type in std::get(*entry.cache) + + if (auto ptr = std::get(*entry.cache)) { + auto napiValue = static_cast(ptr); + auto napiValueWrapped = Napi::Value(env, napiValue); + return structuredClone.Value().Call( + { env.Null(), napiValueWrapped }); + } else { + return env.Null(); + } case static_cast(DynamicFieldsEntryCacheIndex::kDouble): return Napi::Number::New(env, std::get(*entry.cache)); case static_cast(DynamicFieldsEntryCacheIndex::kBool): return Napi::Boolean::New(env, std::get(*entry.cache)); case static_cast(DynamicFieldsEntryCacheIndex::kString): return Napi::String::New(env, std::get(*entry.cache)); + default: + spdlog::error( + "CustomPropertyBinding::Get {:x} {} - unknown cache type {}", formId, + propertyName, entry.cache->index()); } } @@ -77,10 +91,40 @@ void CustomPropertyBinding::Set(Napi::Env env, ScampServer& scampServer, auto newValueDump = NapiHelper::Stringify(env, newValue); auto newValueJson = nlohmann::json::parse(newValueDump); + auto& fynamicFields = refr.GetDynamicFields(); + auto& entry = dynamicFields.Get(propertyName); + + // If there was an object in the cache, we need to free it before setting a + // new value, otherwise we'll have a memory leak + if (entry.cache != std::nullopt) { + switch (entry.cache->index()) { + case static_cast(DynamicFieldsEntryCacheIndex::kVoidPtr): + + // TODO: napi_delete_reference instead!!!!!!!!!!!! + + if (auto ptr = std::get(*entry.cache)) { + uint32_t unrefResult; + auto res = napi_reference_unref(env, static_cast(ptr), + &unrefResult); + if (res != napi_ok) { + spdlog::error("CustomPropertyBinding::Set {:x} {} - failed to " + "unref napi reference, res={}", + formId, propertyName, res); + } else { + spdlog::info("unref result: {}", unrefResult); + } + } + break; + default: + break; + } + } + std::optional newValueCache; switch (newValue.Type()) { case napi_undefined: + newValueCache = static_cast(nullptr); break; case napi_null: newValueCache = static_cast(nullptr); @@ -89,18 +133,62 @@ void CustomPropertyBinding::Set(Napi::Env env, ScampServer& scampServer, newValueCache = newValue.As().Value(); break; case napi_number: + newValueCache = newValue.As().DoubleValue(); break; case napi_string: + newValueCache = newValue.As().Utf8Value(); break; case napi_symbol: + newValueJson = nlohmann::json{}; + newValueDump = "null"; + newValueCache = static_cast(nullptr); + spdlog::error( + "CustomPropertyBinding::Set {:x} {} - symbol type is not supported", + formId, propertyName); break; - case napi_object: - break; + case napi_object: { + auto obj = newValue.As(); + + auto napiValue = static_cast(newValue); + napi_ref ref; + auto res = napi_create_reference(env, napiValue, 1, &ref); + if (res != napi_ok) { + spdlog::error("CustomPropertyBinding::Set {:x} {} - failed to " + "create napi reference, res={}", + formId, propertyName, res); + newValueJson = nlohmann::json{}; + newValueDump = "null"; + newValueCache = static_cast(nullptr); + } else { + newValueJson = nlohmann::json{}; + newValueDump = "null"; + newValueCache = static_cast(ref); + } + + } break; case napi_function: + newValueJson = nlohmann::json{}; + newValueDump = "null"; + newValueCache = static_cast(nullptr); + spdlog::error("CustomPropertyBinding::Set {:x} {} - function type " + "is not supported", + formId, propertyName); break; case napi_external: + newValueJson = nlohmann::json{}; + newValueDump = "null"; + newValueCache = static_cast(nullptr); + spdlog::error("CustomPropertyBinding::Set {:x} {} - external type " + "is not supported", + formId, propertyName); break; case napi_bigint: + newValueJson = nlohmann::json{}; + newValueDump = "null"; + newValueCache = static_cast(nullptr); + spdlog::error("CustomPropertyBinding::Set {:x} {} - bigint type is " + "not supported", + formId, propertyName); break; } diff --git a/skymp5-server/cpp/server_guest_lib/DynamicFields.h b/skymp5-server/cpp/server_guest_lib/DynamicFields.h index cc4693fe1a..90f58400d0 100644 --- a/skymp5-server/cpp/server_guest_lib/DynamicFields.h +++ b/skymp5-server/cpp/server_guest_lib/DynamicFields.h @@ -17,6 +17,7 @@ enum class DynamicFieldsEntryCacheIndex struct DynamicFieldsValueObject { void* napiValue = nullptr; + void *napiRef = nullptr; std::string jsonDump; };