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 86bb0ef192..40c3b54f51 100644 --- a/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp +++ b/skymp5-server/cpp/addon/property_bindings/CustomPropertyBinding.cpp @@ -42,14 +42,41 @@ 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): + +// 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()); + } + } + + return NapiHelper::ParseJson(env, entry.value); } void CustomPropertyBinding::Set(Napi::Env env, ScampServer& scampServer, @@ -64,14 +91,116 @@ 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); + break; + case napi_boolean: + 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: { + 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; + } + 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..90f58400d0 100644 --- a/skymp5-server/cpp/server_guest_lib/DynamicFields.h +++ b/skymp5-server/cpp/server_guest_lib/DynamicFields.h @@ -4,12 +4,31 @@ #include #include #include +#include + +enum class DynamicFieldsEntryCacheIndex +{ + kObject, + kDouble, + kBool, + kString +}; + +struct DynamicFieldsValueObject +{ + void* napiValue = nullptr; + void *napiRef = 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 +46,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();