From d6fa0e51ece0f409901fa8197d7ac46763330ca7 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 24 Jan 2025 20:58:56 +0100 Subject: [PATCH 01/22] ALTV-659: Added magic enum dependency --- .gitmodules | 3 +++ shared/deps/magic_enum | 1 + 2 files changed, 4 insertions(+) create mode 160000 shared/deps/magic_enum diff --git a/.gitmodules b/.gitmodules index 332d23c7..3c5e3b63 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "shared/deps/cpp-sdk"] path = shared/deps/cpp-sdk url = https://github.com/altmp/cpp-sdk.git +[submodule "shared/deps/magic_enum"] + path = shared/deps/magic_enum + url = https://github.com/altmp/magic_enum.git diff --git a/shared/deps/magic_enum b/shared/deps/magic_enum new file mode 160000 index 00000000..2e7e6de5 --- /dev/null +++ b/shared/deps/magic_enum @@ -0,0 +1 @@ +Subproject commit 2e7e6de5ba6171b58db13fc575b5561613ed8108 From 900ae7d872e45185132a85194df16d60439d4e23 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 24 Jan 2025 21:00:49 +0100 Subject: [PATCH 02/22] ALTV-659: Added js bindings support for server and client, move some classes to js side --- .gitignore | 5 +- client/CMakeLists.txt | 4 +- client/src/CV8Resource.cpp | 36 +- client/src/CV8ScriptRuntime.cpp | 15 +- client/src/IImportHandler.cpp | 16 +- client/src/{ => bindings/js}/bootstrap.js | 4 +- client/src/workers/CWorker.cpp | 29 +- client/src/workers/Globals.cpp | 19 +- server/CMakeLists.txt | 4 +- server/src/CNodeResourceImpl.cpp | 35 +- server/src/CNodeScriptRuntime.cpp | 17 +- server/src/bindings/Main.cpp | 8 +- server/src/{ => bindings/js}/bootstrap.js | 42 +- server/src/node-module.cpp | 45 -- shared/V8Class.h | 2 +- shared/V8Module.cpp | 26 + shared/V8Module.h | 82 ++- shared/V8ResourceImpl.cpp | 198 ++++-- shared/V8ResourceImpl.h | 36 +- shared/bindings/BindingsMain.cpp | 17 +- shared/bindings/Quaternion.cpp | 89 --- shared/bindings/Quaternion.js | 97 --- shared/bindings/RGBA.cpp | 75 --- shared/bindings/RGBA.js | 36 -- shared/bindings/Resource.cpp | 18 +- shared/bindings/SharedCppBindings.cpp | 49 ++ shared/bindings/Utils.cpp | 65 +- shared/bindings/Vector2.cpp | 565 ------------------ shared/bindings/Vector2.js | 168 ------ shared/bindings/Vector3.cpp | 154 ----- shared/bindings/Vector3.js | 197 ------ shared/bindings/js/classes/quaternion.js | 183 ++++++ shared/bindings/js/classes/rgba.js | 60 ++ shared/bindings/js/classes/vector2.js | 144 +++++ shared/bindings/js/classes/vector3.js | 153 +++++ shared/bindings/js/helpers/classes.js | 4 + shared/bindings/{Logging.js => js/logging.js} | 2 +- shared/bindings/{Utils.js => js/utils.js} | 2 + shared/helpers/BindingHandler.h | 58 ++ shared/helpers/Bindings.cpp | 22 +- shared/helpers/Bindings.h | 19 + shared/helpers/Convert.h | 6 +- shared/helpers/Filesystem.h | 24 + shared/helpers/JS.cpp | 40 ++ shared/helpers/JS.h | 195 ++++++ shared/helpers/JSBindings.cpp | 57 ++ shared/helpers/JSBindings.h | 74 +++ shared/helpers/Serialization.cpp | 65 +- shared/helpers/Serialization.h | 10 + tools/convert-bindings.js | 168 ++++-- tools/enums-transpiler.js | 4 +- 51 files changed, 1657 insertions(+), 1786 deletions(-) rename client/src/{ => bindings/js}/bootstrap.js (73%) rename server/src/{ => bindings/js}/bootstrap.js (86%) create mode 100644 shared/V8Module.cpp delete mode 100644 shared/bindings/Quaternion.cpp delete mode 100644 shared/bindings/Quaternion.js delete mode 100644 shared/bindings/RGBA.cpp delete mode 100644 shared/bindings/RGBA.js create mode 100644 shared/bindings/SharedCppBindings.cpp delete mode 100644 shared/bindings/Vector2.cpp delete mode 100644 shared/bindings/Vector2.js delete mode 100644 shared/bindings/Vector3.cpp delete mode 100644 shared/bindings/Vector3.js create mode 100644 shared/bindings/js/classes/quaternion.js create mode 100644 shared/bindings/js/classes/rgba.js create mode 100644 shared/bindings/js/classes/vector2.js create mode 100644 shared/bindings/js/classes/vector3.js create mode 100644 shared/bindings/js/helpers/classes.js rename shared/bindings/{Logging.js => js/logging.js} (99%) rename shared/bindings/{Utils.js => js/utils.js} (99%) create mode 100644 shared/helpers/BindingHandler.h create mode 100644 shared/helpers/Filesystem.h create mode 100644 shared/helpers/JS.cpp create mode 100644 shared/helpers/JS.h create mode 100644 shared/helpers/JSBindings.cpp create mode 100644 shared/helpers/JSBindings.h diff --git a/.gitignore b/.gitignore index f8068666..9e599696 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated *.gen -shared/JSBindings.h -shared/JSEnums.h +shared/CompiledEnums.h +shared/CompiledBindings.cpp +shared/bindings-hashes.json node_modules/ diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 88ccb322..2b37cd88 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -43,12 +43,11 @@ endmacro() GroupSources(${PROJECT_SOURCE_DIR}/src "Source Files") GroupSources("../shared" "Shared Files") -make_includable("src/bootstrap.js" "src/bootstrap.js.gen") - include_directories( ${ALTV_JS_CPP_SDK} ${ALTV_JS_DEPS_DIR}/v8/include ../shared + ../shared/deps/magic_enum/include ${PROJECT_SOURCE_DIR}/src ) @@ -99,6 +98,7 @@ if(DYNAMIC_BUILD EQUAL 1) ${PROJECT_NAME} SHARED ${PROJECT_SOURCE_FILES} ${PROJECT_SHARED_FILES} + ../shared/CompiledBindings.cpp ) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 diff --git a/client/src/CV8Resource.cpp b/client/src/CV8Resource.cpp index c63bcfd9..b3706250 100644 --- a/client/src/CV8Resource.cpp +++ b/client/src/CV8Resource.cpp @@ -26,8 +26,6 @@ #include "workers/CWorker.h" -#include "JSBindings.h" - extern void StaticRequire(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -77,10 +75,6 @@ void CV8ResourceImpl::ProcessDynamicImports() dynamicImports.clear(); } -extern std::string bootstrap_code = -#include "bootstrap.js.gen" -; - CV8ResourceImpl::CV8ResourceImpl(alt::IResource* resource, v8::Isolate* isolate) : V8ResourceImpl(isolate, resource) { v8::Locker locker(isolate); @@ -96,8 +90,6 @@ CV8ResourceImpl::CV8ResourceImpl(alt::IResource* resource, v8::Isolate* isolate) ctx->SetAlignedPointerInEmbedderData(1, resource); } -extern V8Module altModule; - bool CV8ResourceImpl::Start() { if (resource->GetMain().empty()) return false; @@ -111,8 +103,11 @@ bool CV8ResourceImpl::Start() v8::Local ctx = GetContext(); v8::Context::Scope context_scope(ctx); - V8ResourceImpl::Start(); - SetupScriptGlobals(); + V8ResourceImpl::Initialize(); + V8ResourceImpl::InitializeBindings(js::Binding::Scope::CLIENT, V8Module::Get("alt")); + + js::Binding bootstrapper = js::Binding::Get("client/bootstrap.js"); + if (!bootstrapper.IsValid()) return false; std::string path = resource->GetMain(); Log::Info << "[V8] Starting script " << path << Log::Endl; @@ -123,23 +118,18 @@ bool CV8ResourceImpl::Start() v8::MaybeLocal maybeModule; v8::ScriptOrigin scriptOrigin(isolate, V8Helpers::JSValue(""), 0, 0, false, -1, v8::Local(), false, false, true, v8::Local()); - v8::ScriptCompiler::Source source{V8Helpers::JSValue(bootstrap_code), scriptOrigin}; + v8::ScriptCompiler::Source source{V8Helpers::JSValue(bootstrapper.GetSource()), scriptOrigin}; maybeModule = v8::ScriptCompiler::CompileModule(isolate, &source); if (maybeModule.IsEmpty()) return false; v8::Local curModule = maybeModule.ToLocalChecked(); - ctx->Global()->Set(ctx, V8Helpers::JSValue("__internal_get_exports"), - v8::Function::New(ctx, &StaticRequire).ToLocalChecked()); - ctx->Global()->Set(ctx, V8Helpers::JSValue("__internal_bindings_code"), - V8Helpers::JSValue(JSBindings::GetBindingsCode())); - ctx->Global()->Set(ctx, V8Helpers::JSValue("__internal_main_path"), V8Helpers::JSValue(path)); - ctx->Global()->Set(ctx, V8Helpers::JSValue("__internal_start_file"), - v8::Function::New(ctx, &StartFile).ToLocalChecked()); + js::TemporaryGlobalExtension internalGetExports(GetContext(), "__internal_get_exports", v8::Function::New(ctx, &StaticRequire).ToLocalChecked()); + js::TemporaryGlobalExtension internalMainPath(GetContext(), "__internal_main_path", V8Helpers::JSValue(path)); + js::TemporaryGlobalExtension internalStartFile(GetContext(), "__internal_start_file", v8::Function::New(ctx, &StartFile).ToLocalChecked()); - bool result = InstantiateModule(curModule); - if (!result) return false; + if (!InstantiateModule(curModule)) return false; Log::Info << "[V8] Started script " << path << Log::Endl; return true; @@ -568,14 +558,14 @@ v8::MaybeLocal EvaluateSyntheticModule(v8::Local context v8::Local CV8ResourceImpl::CreateSyntheticModule(const std::string& name, v8::Local exportValue) { v8::Local defaultExport = V8Helpers::JSValue("default"); - v8::MemorySpan> exports = { &defaultExport, 1 }; + v8::MemorySpan> exports = { &defaultExport, 1 }; v8::Local syntheticModule = v8::Module::CreateSyntheticModule( - isolate, V8Helpers::JSValue(name), exports, &EvaluateSyntheticModule); + isolate, V8Helpers::JSValue(name), exports, &EvaluateSyntheticModule); syntheticModuleExports.insert({ syntheticModule->GetIdentityHash(), V8Helpers::CPersistent(isolate, exportValue) - }); + }); return syntheticModule; } diff --git a/client/src/CV8ScriptRuntime.cpp b/client/src/CV8ScriptRuntime.cpp index 6188555d..e120c909 100644 --- a/client/src/CV8ScriptRuntime.cpp +++ b/client/src/CV8ScriptRuntime.cpp @@ -3,16 +3,16 @@ #include "inspector/CV8InspectorChannel.h" #include "V8Module.h" #include "events/Events.h" -#include "CProfiler.h" +#include "CProfiler.h" #include CV8ScriptRuntime::CV8ScriptRuntime() { // !!! Don't change these without adjusting bytecode module !!! v8::V8::SetFlagsFromString("--harmony-import-assertions --short-builtin-calls --no-lazy --no-flush-bytecode"); - platform = v8::platform::NewDefaultPlatform(); - - v8::LogEventCallback; + platform = v8::platform::NewDefaultPlatform(); + + v8::LogEventCallback; v8::V8::InitializePlatform(platform.get()); v8::V8::InitializeICU((alt::ICore::Instance().GetClientPath() + "/libs/icudtl_v8.dat").c_str()); @@ -218,10 +218,9 @@ CV8ScriptRuntime::CV8ScriptRuntime() v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); - V8Class::LoadAll(isolate); - + V8Class::Initialize(isolate); extern V8Module altModule, nativesModule, sharedModule; - V8Module::Add(isolate, { altModule, nativesModule, sharedModule }); + V8Module::Add({ altModule, nativesModule, sharedModule }); } RegisterEvents(); @@ -250,6 +249,8 @@ void CV8ScriptRuntime::OnDispose() if(CProfiler::Instance().IsEnabled()) CProfiler::Instance().Dump(alt::ICore::Instance().GetClientPath()); + V8Class::UnloadAll(isolate); + V8Module::Cleanup(isolate); CV8ScriptRuntime::SetInstance(nullptr); delete this; } diff --git a/client/src/IImportHandler.cpp b/client/src/IImportHandler.cpp index 82451f7f..583e8bb2 100644 --- a/client/src/IImportHandler.cpp +++ b/client/src/IImportHandler.cpp @@ -13,7 +13,7 @@ static inline v8::MaybeLocal CompileESM(v8::Isolate* isolate, const static inline bool IsSystemModule(v8::Isolate* isolate, const std::string& name) { - return V8Module::Exists(isolate, name); + return V8Module::Exists(name); } static inline v8::MaybeLocal WrapModule(v8::Isolate* isolate, const std::deque& _exportKeys, const std::string& name, bool exportAsDefault = false) @@ -44,7 +44,7 @@ static inline v8::MaybeLocal WrapModule(v8::Isolate* isolate, const bool IImportHandler::IsValidModule(const std::string& name) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); - if(V8Module::Exists(isolate, name)) return true; + if(V8Module::Exists(name)) return true; alt::IResource* resource = alt::ICore::Instance().GetResource(name); if(resource) return true; @@ -66,10 +66,10 @@ std::deque IImportHandler::GetModuleKeys(const std::string& name) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Local context = isolate->GetEnteredOrMicrotaskContext(); - auto v8module = V8Module::All()[isolate].find(name); - if(v8module != V8Module::All()[isolate].end()) + if(V8Module::Exists(name)) { - auto _exports = v8module->second->GetExports(isolate, context); + V8Module& module = V8Module::Get(name); + auto _exports = module.GetModuleExports(isolate); v8::Local v8Keys = _exports->GetOwnPropertyNames(context).ToLocalChecked(); std::deque keys; @@ -131,10 +131,10 @@ v8::MaybeLocal IImportHandler::Require(const std::string& name) auto it = requiresMap.find(name); if(it != requiresMap.end()) return it->second.Get(isolate); - auto v8module = V8Module::All()[isolate].find(name); - if(v8module != V8Module::All()[isolate].end()) + if (V8Module::Exists(name)) { - auto _exports = v8module->second->GetExports(isolate, isolate->GetEnteredOrMicrotaskContext()); + V8Module& module = V8Module::Get(name); + auto _exports = module.GetModuleExports(isolate); requiresMap.insert({ name, V8Helpers::CPersistent{ isolate, _exports } }); return _exports; diff --git a/client/src/bootstrap.js b/client/src/bindings/js/bootstrap.js similarity index 73% rename from client/src/bootstrap.js rename to client/src/bindings/js/bootstrap.js index ff0eae89..f3368667 100644 --- a/client/src/bootstrap.js +++ b/client/src/bindings/js/bootstrap.js @@ -3,9 +3,7 @@ import * as alt from "alt"; import * as native from "natives"; // Load the global bindings code -const bindingsGlobal = {}; -new Function("alt", "native", "__global", __internal_bindings_code)(alt, native, bindingsGlobal); -__setLogFunction(bindingsGlobal.genericLog); +__setLogFunction(genericLog); const extraBootstrapFile = __getExtraBootstrapFile(); if(extraBootstrapFile.length !== 0) new Function("alt", "native", extraBootstrapFile)(alt, native); diff --git a/client/src/workers/CWorker.cpp b/client/src/workers/CWorker.cpp index f8c101b8..ac738114 100644 --- a/client/src/workers/CWorker.cpp +++ b/client/src/workers/CWorker.cpp @@ -6,7 +6,6 @@ #include "V8Module.h" #include "WorkerTimer.h" #include "V8FastFunction.h" -#include "JSBindings.h" #include @@ -214,7 +213,6 @@ void CWorker::SetupContext() ctx->SetAlignedPointerInEmbedderData(2, this); } -extern std::string bootstrap_code; bool CWorker::SetupScript() { bool failed = false; @@ -223,16 +221,23 @@ bool CWorker::SetupScript() [&]() { v8::Local ctx = context.Get(isolate); + + js::Binding bootstrapper = js::Binding::Get("client/bootstrap.js"); + if (!bootstrapper.IsValid()) { + failed = true; + return true; + } + v8::MaybeLocal maybeModule; v8::ScriptOrigin scriptOrigin(V8Helpers::JSValue(""), 0, 0, false, -1, v8::Local(), false, false, true, v8::Local()); - v8::ScriptCompiler::Source source{ V8Helpers::JSValue(bootstrap_code), scriptOrigin }; + v8::ScriptCompiler::Source source{ V8Helpers::JSValue(bootstrapper.GetSource()), scriptOrigin }; maybeModule = v8::ScriptCompiler::CompileModule(isolate, &source); if(maybeModule.IsEmpty()) { EmitError("Failed to compile worker module"); failed = true; - return; + return true; } auto mod = maybeModule.ToLocalChecked(); @@ -242,7 +247,7 @@ bool CWorker::SetupScript() { EmitError("Failed to instantiate worker module"); failed = true; - return; + return true; } // Evaluate the code @@ -251,8 +256,10 @@ bool CWorker::SetupScript() { EmitError("Failed to evaluate worker module"); failed = true; - return; + return true; } + + return false; }); if(!error.empty() || failed) { @@ -266,8 +273,8 @@ bool CWorker::SetupScript() void CWorker::DestroyIsolate() { while(isolate->IsInUse()) isolate->Exit(); - V8Module::Clear(isolate); V8Class::UnloadAll(isolate); + V8Module::Cleanup(isolate); V8FastFunction::UnloadAll(isolate); context.Reset(); GetMainEventHandler().Reset(); @@ -283,11 +290,10 @@ void CWorker::SetupGlobals() { v8::Local global = context.Get(isolate)->Global(); - V8Class::LoadAll(isolate); - V8Module::Add(isolate, altWorker, { "alt" }); - V8Module::Add(isolate, altWorkerNatives); + V8Class::Initialize(isolate); + V8Module::Initialize(isolate); - auto alt = altWorker.GetExports(isolate, context.Get(isolate)); + auto alt = V8Module::Get("alt").GetModuleExports(isolate); auto console = global->Get(context.Get(isolate), V8Helpers::JSValue("console")).ToLocalChecked().As(); if(!console.IsEmpty()) { @@ -304,7 +310,6 @@ void CWorker::SetupGlobals() V8Helpers::RegisterFunc(global, "clearTimeout", &ClearTimer); global->Set(context.Get(isolate), V8Helpers::JSValue("__internal_get_exports"), v8::Function::New(context.Get(isolate), &StaticRequire).ToLocalChecked()); - global->Set(context.Get(isolate), V8Helpers::JSValue("__internal_bindings_code"), V8Helpers::JSValue(JSBindings::GetBindingsCode())); global->Set(context.Get(isolate), V8Helpers::JSValue("__internal_main_path"), V8Helpers::JSValue(filePath)); } diff --git a/client/src/workers/Globals.cpp b/client/src/workers/Globals.cpp index 4c99d100..79a897f8 100644 --- a/client/src/workers/Globals.cpp +++ b/client/src/workers/Globals.cpp @@ -1,4 +1,8 @@ #include "Globals.h" + +#include +#include + #include "CWorker.h" #include "V8Helpers.h" #include "V8Module.h" @@ -122,15 +126,16 @@ void GetSharedArrayBuffer(const v8::FunctionCallbackInfo& info) } extern V8Module altModule; -extern V8Class v8File, v8RGBA, v8Vector2, v8Vector3, v8Utils, v8Resource; +extern V8Class v8File, v8Utils, v8Resource; extern V8Module altWorker("alt-worker", nullptr, - { v8File, v8RGBA, v8Vector2, v8Vector3, v8Utils, v8Resource }, + { v8File,v8Utils, v8Resource }, [](v8::Local ctx, v8::Local exports) { v8::Isolate* isolate = ctx->GetIsolate(); + V8ResourceImpl* resource = V8ResourceImpl::Get(ctx); - auto alt = altModule.GetExports(isolate, ctx); + auto alt = altModule.GetModuleExports(isolate); auto InheritFromAlt = [&](const char* name) { auto original = alt->Get(ctx, V8Helpers::JSValue(name)); @@ -151,6 +156,14 @@ extern V8Module altWorker("alt-worker", V8Helpers::RegisterFunc(exports, "clearTimer", &ClearTimer); V8Helpers::RegisterFunc(exports, "getSharedArrayBuffer", &::GetSharedArrayBuffer); + if (resource->IsBindingsInitialized()) + { + V8Helpers::RegisterBindingExport(exports, "Vector2", js::BindingExport::VECTOR2_CLASS); + V8Helpers::RegisterBindingExport(exports, "Vector3", js::BindingExport::VECTOR3_CLASS); + V8Helpers::RegisterBindingExport(exports, "RGBA", js::BindingExport::RGBA_CLASS); + V8Helpers::RegisterBindingExport(exports, "Quaternion", js::BindingExport::QUATERNION_CLASS); + } + V8_OBJECT_SET_BOOLEAN(exports, "isWorker", true); // *** All inherited functions from the alt module diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 38332edf..a20bccac 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -63,8 +63,6 @@ endmacro() GroupSources(${PROJECT_SOURCE_DIR}/src "Source Files") GroupSources("../shared" "Shared Files") -make_includable("src/bootstrap.js" "src/bootstrap.js.gen") - include_directories( src deps/nodejs/include @@ -74,6 +72,7 @@ include_directories( deps/nodejs/deps/simdjson ../shared ../shared/deps + ../shared/deps/magic_enum/include ) if(WIN32 AND MSVC) @@ -144,6 +143,7 @@ add_library( ${PROJECT_NAME} SHARED ${PROJECT_SOURCE_FILES} ${PROJECT_SHARED_FILES} + ../shared/CompiledBindings.cpp ) add_dependencies(${PROJECT_NAME} alt-sdk js-bindings) diff --git a/server/src/CNodeResourceImpl.cpp b/server/src/CNodeResourceImpl.cpp index 0d05efe4..fba9cafa 100644 --- a/server/src/CNodeResourceImpl.cpp +++ b/server/src/CNodeResourceImpl.cpp @@ -1,12 +1,13 @@ #include "stdafx.h" #include "CNodeResourceImpl.h" + +#include "helpers/JSBindings.h" + #include "CNodeScriptRuntime.h" #include "V8Module.h" #include "V8Helpers.h" -#include "JSBindings.h" - static void ResourceLoaded(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); @@ -22,10 +23,6 @@ static void ResourceLoaded(const v8::FunctionCallbackInfo& info) } } -static const char bootstrap_code[] = -#include "bootstrap.js.gen" - ; - CNodeResourceImpl::CNodeResourceImpl(CNodeScriptRuntime* _runtime, alt::IResource* resource) : V8ResourceImpl(nullptr, resource), runtime(_runtime) { uvLoop = new uv_loop_t; @@ -53,22 +50,26 @@ bool CNodeResourceImpl::Start() auto _context = GetContext(); v8::Context::Scope scope(_context); - _context->Global()->Set(_context, V8Helpers::JSValue("__resourceLoaded"), v8::Function::New(_context, &ResourceLoaded).ToLocalChecked()); - _context->Global()->Set(_context, V8Helpers::JSValue("__internal_bindings_code"), V8Helpers::JSValue(JSBindings::GetBindingsCode())); - - V8Class::LoadAll(isolate); - V8ResourceImpl::Start(); - V8ResourceImpl::SetupScriptGlobals(); - - node::ThreadId threadId = node::AllocateEnvironmentThreadId(); - auto flags = static_cast(node::EnvironmentFlags::kNoFlags); - auto inspector = node::GetInspectorParentHandle(runtime->GetParentEnv(), threadId, resource->GetName().c_str()); + V8ResourceImpl::Initialize(); std::vector args{ resource->GetName() }; std::vector execArgs{ }; + auto flags = static_cast(node::EnvironmentFlags::kNoFlags); + node::ThreadId threadId = node::AllocateEnvironmentThreadId(); + auto inspector = node::GetInspectorParentHandle(runtime->GetParentEnv(), threadId, resource->GetName().c_str()); env = node::CreateEnvironment(nodeData, _context, args, execArgs, flags, threadId, std::move(inspector)); - node::LoadEnvironment(env, bootstrap_code); + + V8ResourceImpl::InitializeBindings(js::Binding::Scope::SERVER, V8Module::Get("alt")); + + const js::Binding& bootstrapper = js::Binding::Get("server/bootstrap.js"); + if (!bootstrapper.IsValid()) return false; + + js::TemporaryGlobalExtension altModule(_context, "__altModule", V8Module::Get("alt").GetModuleExports(isolate)); + js::TemporaryGlobalExtension cppBindings(_context, "__cppBindings", V8Module::Get("cppBindings").GetModuleExports(isolate)); + js::TemporaryGlobalExtension resourceLoaded(_context, "__resourceLoaded", v8::Function::New(_context, &ResourceLoaded).ToLocalChecked()); + + node::LoadEnvironment(env, bootstrapper.GetSource()); // Not sure it's needed anymore asyncResource.Reset(isolate, v8::Object::New(isolate)); diff --git a/server/src/CNodeScriptRuntime.cpp b/server/src/CNodeScriptRuntime.cpp index 84e77c44..4793c480 100644 --- a/server/src/CNodeScriptRuntime.cpp +++ b/server/src/CNodeScriptRuntime.cpp @@ -76,21 +76,8 @@ void CNodeScriptRuntime::OnTick() void CNodeScriptRuntime::OnDispose() { - /*{ - v8::SealHandleScope seal(isolate); - - do { - uv_run(uv_default_loop(), UV_RUN_DEFAULT); - platform->DrainTasks(isolate); - } while (uv_loop_alive(uv_default_loop())); - } - platform->UnregisterIsolate(isolate); - isolate->Dispose(); - node::FreePlatform(platform.release()); - v8::V8::Dispose(); - v8::V8::ShutdownPlatform(); - */ - + V8Module::Cleanup(isolate); + V8Class::UnloadAll(isolate); if(CProfiler::Instance().IsEnabled()) CProfiler::Instance().Dump("./"); } diff --git a/server/src/bindings/Main.cpp b/server/src/bindings/Main.cpp index b17295fb..ad69543a 100644 --- a/server/src/bindings/Main.cpp +++ b/server/src/bindings/Main.cpp @@ -841,9 +841,9 @@ static void HasBenefit(const v8::FunctionCallbackInfo& info) V8_ARG_TO_UINT(1, benefit); V8_RETURN_BOOLEAN(alt::ICore::Instance().HasBenefit((alt::Benefit)benefit)); -} - -static void PrintHealth(const v8::FunctionCallbackInfo& info) +} + +static void PrintHealth(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); resource->PrintHealth(); @@ -941,7 +941,7 @@ extern V8Module V8Helpers::RegisterFunc(exports, "getMigrationDistance", &GetMigrationDistance); V8Helpers::RegisterFunc(exports, "setMigrationDistance", &SetMigrationDistance); - V8Helpers::RegisterFunc(exports, "hasBenefit", &HasBenefit); + V8Helpers::RegisterFunc(exports, "hasBenefit", &HasBenefit); V8Helpers::RegisterFunc(exports, "printHealth", &PrintHealth); diff --git a/server/src/bootstrap.js b/server/src/bindings/js/bootstrap.js similarity index 86% rename from server/src/bootstrap.js rename to server/src/bindings/js/bootstrap.js index e876bb35..32a49ec7 100644 --- a/server/src/bootstrap.js +++ b/server/src/bindings/js/bootstrap.js @@ -6,8 +6,10 @@ const { translators } = internalRequire('internal/modules/esm/translators'); const { ModuleWrap } = internalRequire('internal/test/binding').internalBinding('module_wrap'); const BuildInModule = internalRequire('module'); +const alt = __altModule; +const cppBindings = __cppBindings; + const path = require('path'); -const alt = process._linkedBinding('alt'); const dns = require('dns'); const url = require('url'); const inspector = require('inspector'); @@ -31,10 +33,7 @@ const inspector = require('inspector'); try { setupImports(esmLoader); - // Load the global bindings code - const bindingsGlobal = {}; - new Function('alt', '__global', __internal_bindings_code)(alt, bindingsGlobal); - __setLogFunction(bindingsGlobal.genericLog); + __setLogFunction(genericLog); const config = alt.Resource.current.config; if (config.inspector) { @@ -73,19 +72,10 @@ function setupImports(esmLoader) { const altResourceInternalPrefix = 'altresource'; const altModuleImportPrefix = 'altinternal'; - const altModules = new Map([ - ['alt', 'alt'], - ['alt-server','alt'], - ['alt-shared', 'altShared'], - ]); - - const modulesCache = new Map(); - translators.set(altResourceInternalPrefix, async function(url) { const name = url.slice(altResourceInternalPrefix.length + 1); // Remove prefix const exports = alt.Resource.getByName(name).exports; - // TODO: cache return new ModuleWrap(url, undefined, Object.keys(exports), function() { for (const exportName in exports) { let value; @@ -98,16 +88,11 @@ function setupImports(esmLoader) { }); translators.set(altModuleImportPrefix, async function(url) { - const cached = modulesCache.get(url); - if (cached !== undefined) { - return cached; - } - const name = url.slice(altModuleImportPrefix.length + 1); // Remove prefix - const bindings = process._linkedBinding(name); + const bindings = cppBindings.getBuiltinModule(name); const exportKeys = Object.keys(bindings); - const module = new ModuleWrap(url, undefined, [...exportKeys, 'default'], function() { + return new ModuleWrap(url, undefined, [...exportKeys, 'default'], function() { for (let i = 0; i < exportKeys.length; ++i) { const key = exportKeys[i]; this.setExport(key, bindings[key]); @@ -115,9 +100,6 @@ function setupImports(esmLoader) { this.setExport('default', bindings); }); - - modulesCache.set(url, module); - return module; }); const _warningPackages = { @@ -126,13 +108,11 @@ function setupImports(esmLoader) { const altLoader = { resolveSync(specifier, context, importAttributes) { - const altModule = altModules.get(specifier); - if (altModule !== undefined) { + if (cppBindings.getBuiltinModule(specifier) !== null) return { - url: `${altModuleImportPrefix}:${altModule}`, + url: `${altModuleImportPrefix}:${specifier}`, shortCircuit: true }; - } const hasAltPrefix = specifier.startsWith(`${altResourceImportPrefix}:`); @@ -148,7 +128,7 @@ function setupImports(esmLoader) { if (hasAltPrefix) { const specifierWithoutPrefix = specifier.slice(altResourceImportPrefix.length + 1); - + if (!alt.hasResource(specifierWithoutPrefix)) { alt.logError(`Trying to import resource '${specifierWithoutPrefix}' that doesn't exist`); } else { @@ -210,9 +190,9 @@ function setupImports(esmLoader) { // CJS require hook const _origModuleLoad = BuildInModule._load; BuildInModule._load = function _load(request, parent, isMain) { - const altModule = altModules.get(request); + const altModule = cppBindings.getBuiltinModule(request); if (altModule !== undefined) { - return process._linkedBinding(altModule); + return altModule; } return _origModuleLoad(request, parent, isMain); diff --git a/server/src/node-module.cpp b/server/src/node-module.cpp index cb084254..a69304cc 100644 --- a/server/src/node-module.cpp +++ b/server/src/node-module.cpp @@ -5,51 +5,6 @@ #include "V8Module.h" #include "CNodeScriptRuntime.h" -/*static void NodeStop() -{ - { - //v8::Isolate::Scope isolateScope(isolate); - //v8::SealHandleScope seal(isolate); - //v8::platform::PumpMessageLoop(platform, isolate); - //node::EmitBeforeExit(m_env); - - //if (uv_loop_alive(m_env->event_loop())) { - // uv_run(m_env->event_loop(), UV_RUN_NOWAIT); - //} - } - - //node::EmitExit(m_env); - //node::RunAtExit(m_env); - //node::FreeEnvironment(m_env); - - //isolate->Dispose(); - //v8::V8::Dispose(); -}*/ - -extern V8Module v8Alt; -namespace main -{ - static void Initialize(v8::Local exports, v8::Local module, v8::Local context, void*) - { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - - v8Alt.Register(isolate, context, exports); - } - NODE_MODULE_LINKED(alt, Initialize) -} // namespace main - -extern V8Module sharedModule; -namespace shared -{ - static void InitializeShared(v8::Local exports, v8::Local module, v8::Local context, void*) - { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - - sharedModule.Register(isolate, context, exports); - } - NODE_MODULE_LINKED(altShared, InitializeShared) -} // namespace shared - static void CommandHandler(const std::vector& args) { if(args.size() == 0) diff --git a/shared/V8Class.h b/shared/V8Class.h index d4e3708f..29b79709 100644 --- a/shared/V8Class.h +++ b/shared/V8Class.h @@ -101,7 +101,7 @@ class V8Class v8::Local New(v8::Local ctx, std::vector>& args); - static void LoadAll(v8::Isolate* isolate) + static void Initialize(v8::Isolate* isolate) { for(auto& p : All()) p.second->Load(isolate); } diff --git a/shared/V8Module.cpp b/shared/V8Module.cpp new file mode 100644 index 00000000..91324b9c --- /dev/null +++ b/shared/V8Module.cpp @@ -0,0 +1,26 @@ +#include "V8Module.h" +#include "V8ResourceImpl.h" + +void V8Module::Register(v8::Isolate* isolate, v8::Local exports) +{ + v8::Local context = isolate->GetCurrentContext(); + + if (parentModule) parentModule->Register(isolate, exports); + for(V8Class* c : classes) c->Register(isolate, context, exports); + creator(context, exports); +} + +void V8Module::Initialize(v8::Isolate* isolate) +{ + for(auto& [name, module] : GetAll()) + { + v8::Local exports = v8::Object::New(isolate); + module->Register(isolate, exports); + module->SetModuleExports(isolate, exports); + } +} + +void V8Module::Cleanup(v8::Isolate* isolate) +{ + for(auto& [_, module] : GetAll()) module->modExports.Get(isolate).Clear(); +} \ No newline at end of file diff --git a/shared/V8Module.h b/shared/V8Module.h index 36ff76dc..2843e85c 100644 --- a/shared/V8Module.h +++ b/shared/V8Module.h @@ -1,80 +1,70 @@ #pragma once -#include -#include +#include #include #include #include "V8Class.h" #include +class V8ResourceImpl; + class V8Module { using Callback = std::function ctx, v8::Local exports)>; - using ModuleMap = std::unordered_map>; + using ModuleMap = std::unordered_map; public: - static ModuleMap& All() + static ModuleMap& GetAll() { - static ModuleMap all; - return all; + static ModuleMap modules; + return modules; } - static void Add(v8::Isolate* isolate, V8Module& mod, const std::vector& extraNames = {}) + V8Module(std::string moduleName, V8Module* parent, std::initializer_list> _classes, Callback fn) + : moduleName(moduleName), + creator(fn), parentModule(parent) { - All()[isolate][mod.moduleName] = &mod; - for(auto& name : extraNames) + for (auto& ref : _classes) classes.insert(&ref.get()); + GetAll().insert_or_assign(moduleName, this); + } + + static void Add(std::initializer_list> _modules) + { + for (auto& mod : _modules) { - All()[isolate][name] = &mod; + V8Module* module = &mod.get(); + GetAll().insert_or_assign(module->GetModuleName(), module); } } - static void Add(v8::Isolate* isolate, std::initializer_list> modules) + const std::string& GetModuleName() const { return moduleName; } + + static V8Module& Get(const std::string& name) { - for(auto& m : modules) All()[isolate][m.get().moduleName] = &m.get(); + return *GetAll().at(name); } - static void Clear(v8::Isolate* isolate) + static bool Exists(const std::string& name) { - All().erase(isolate); + return GetAll().contains(name); } - static bool Exists(v8::Isolate* isolate, const std::string& name) + static void Erase(const std::string& modName) { - if(All()[isolate].find(name) == All()[isolate].end()) return false; - else - return true; + GetAll().erase(modName); } + inline void SetModuleExports(v8::Isolate* isolate, v8::Local exports) { modExports.Reset(isolate, exports); }; + inline v8::Local GetModuleExports(v8::Isolate* isolate) const { return modExports.Get(isolate); }; + + void Register(v8::Isolate* isolate, v8::Local exports); + static void Initialize(v8::Isolate* isolate); + static void Cleanup(v8::Isolate* isolate); + +private: std::string moduleName; + v8::Persistent modExports; std::unordered_set classes; Callback creator; V8Module* parentModule; - - template - V8Module(std::string moduleName, V8Module* parent, std::initializer_list> _classes, Callback fn) - : moduleName(moduleName), creator(fn), parentModule(parent) - { - for(auto& c : _classes) classes.insert(&c.get()); - - // All()[moduleName] = this; - } - - void Register(v8::Isolate* isolate, v8::Local context, v8::Local exports) - { - if(parentModule) parentModule->Register(isolate, context, exports); - // Load all classes - for(auto c : classes) - { - c->Register(isolate, context, exports); - } - - creator(context, exports); - } - - v8::Local GetExports(v8::Isolate* isolate, v8::Local context) - { - v8::Local _exports = v8::Object::New(isolate); - Register(isolate, context, _exports); - return _exports; - } }; diff --git a/shared/V8ResourceImpl.cpp b/shared/V8ResourceImpl.cpp index b36a5e57..3ea9ed58 100644 --- a/shared/V8ResourceImpl.cpp +++ b/shared/V8ResourceImpl.cpp @@ -3,6 +3,7 @@ #include "cpp-sdk/objects/IVehicle.h" #include "V8ResourceImpl.h" +#include "helpers/JSBindings.h" #ifdef ALT_SERVER_API #include "CNodeResourceImpl.h" @@ -16,15 +17,11 @@ using namespace alt; -extern V8Class v8Vector3, v8Vector2, v8RGBA, v8BaseObject, v8Quaternion; +extern V8Class v8BaseObject; + bool V8ResourceImpl::Start() { - vector3Class.Reset(isolate, v8Vector3.JSValue(isolate, GetContext())); - vector2Class.Reset(isolate, v8Vector2.JSValue(isolate, GetContext())); - quaternionClass.Reset(isolate, v8Quaternion.JSValue(isolate, GetContext())); - rgbaClass.Reset(isolate, v8RGBA.JSValue(isolate, GetContext())); baseObjectClass.Reset(isolate, v8BaseObject.JSValue(isolate, GetContext())); - return true; } @@ -60,15 +57,13 @@ bool V8ResourceImpl::Stop() players.Reset(); vehicles.Reset(); - vector3Class.Reset(); - vector2Class.Reset(); - quaternionClass.Reset(); - rgbaClass.Reset(); baseObjectClass.Reset(); objects.Reset(); context.Reset(); + js::Binding::CleanupForResource(this); + return true; } @@ -80,9 +75,9 @@ void V8ResourceImpl::OnTick() for(auto& id : oldTimers) { auto it = timers.find(id); - if(it == timers.end()) - continue; - + if(it == timers.end()) + continue; + auto timer = it->second; timers.erase(it); delete timer; @@ -199,66 +194,72 @@ v8::Local V8ResourceImpl::GetBaseObjectOrNull(alt::IBaseObject* handl v8::Local V8ResourceImpl::CreateVector3(alt::Vector3f vec) { - std::vector> args{ V8Helpers::JSValue(vec[0]), V8Helpers::JSValue(vec[1]), V8Helpers::JSValue(vec[2]) }; + v8::Local vector3 = bindingHandler->GetBindingExport(js::BindingExport::VECTOR3_CLASS); + if(vector3.IsEmpty()) return v8::Local(); - return v8Vector3.CreateInstance(isolate, GetContext(), args); + std::vector> args{ V8Helpers::JSValue(vec[0]), V8Helpers::JSValue(vec[1]), V8Helpers::JSValue(vec[2]) }; + return vector3->NewInstance(GetContext(), args.size(), args.data()).ToLocalChecked(); } v8::Local V8ResourceImpl::CreateVector2(alt::Vector2f vec) { - std::vector> args{ V8Helpers::JSValue(vec[0]), V8Helpers::JSValue(vec[1]) }; + v8::Local vector2 = bindingHandler->GetBindingExport(js::BindingExport::VECTOR2_CLASS); + if(vector2.IsEmpty()) return v8::Local(); - return v8Vector2.CreateInstance(isolate, GetContext(), args); + std::vector> args{ V8Helpers::JSValue(vec[0]), V8Helpers::JSValue(vec[1]) }; + return vector2->NewInstance(GetContext(), args.size(), args.data()).ToLocalChecked(); } -v8::Local V8ResourceImpl::CreateQuaternion(alt::Quaternion quat) +v8::Local V8ResourceImpl::CreateQuaternion(alt::Quaternion quaternion) { - std::vector> args{ V8Helpers::JSValue(quat.x), V8Helpers::JSValue(quat.y), V8Helpers::JSValue(quat.z), V8Helpers::JSValue(quat.w) }; + v8::Local quaternionClass = bindingHandler->GetBindingExport(js::BindingExport::QUATERNION_CLASS); + if(quaternionClass.IsEmpty()) return v8::Local(); - return v8Quaternion.CreateInstance(isolate, GetContext(), args); + std::array, 4> args = { V8Helpers::JSValue(quaternion.x), V8Helpers::JSValue(quaternion.y), V8Helpers::JSValue(quaternion.z), V8Helpers::JSValue(quaternion.w) }; + return quaternionClass->NewInstance(GetContext(), args.size(), args.data()).ToLocalChecked(); } v8::Local V8ResourceImpl::CreateRGBA(alt::RGBA rgba) { - std::vector> args{ V8Helpers::JSValue(rgba.r), V8Helpers::JSValue(rgba.g), V8Helpers::JSValue(rgba.b), V8Helpers::JSValue(rgba.a) }; - - return V8Helpers::New(isolate, GetContext(), rgbaClass.Get(isolate), args); -} - -//bool V8ResourceImpl::IsVector3(v8::Local val) -//{ -// bool result = false; -// val->InstanceOf(GetContext(), vector3Class.Get(isolate)).To(&result); -// return result; -//} -// -//bool V8ResourceImpl::IsVector2(v8::Local val) -//{ -// bool result = false; -// val->InstanceOf(GetContext(), vector2Class.Get(isolate)).To(&result); -// return result; -//} -// -//bool V8ResourceImpl::IsQuaternion(v8::Local val) -//{ -// bool result = false; -// val->InstanceOf(GetContext(), quaternionClass.Get(isolate)).To(&result); -// return result; -//} -// -//bool V8ResourceImpl::IsRGBA(v8::Local val) -//{ -// bool result = false; -// val->InstanceOf(GetContext(), rgbaClass.Get(isolate)).To(&result); -// return result; -//} -// -//bool V8ResourceImpl::IsBaseObject(v8::Local val) -//{ -// bool result = false; -// val->InstanceOf(GetContext(), baseObjectClass.Get(isolate)).To(&result); -// return result; -//} + v8::Local rgbaClass = bindingHandler->GetBindingExport(js::BindingExport::RGBA_CLASS); + if(rgbaClass.IsEmpty()) return v8::Local(); + + std::array, 4> args = { V8Helpers::JSValue(rgba.r), V8Helpers::JSValue(rgba.g), V8Helpers::JSValue(rgba.b), V8Helpers::JSValue(rgba.a) }; + return rgbaClass->NewInstance(GetContext(), args.size(), args.data()).ToLocalChecked(); +} + +bool V8ResourceImpl::IsVector3(v8::Local val) +{ + v8::Local vector3 = bindingHandler->GetBindingExport(js::BindingExport::VECTOR3_CLASS); + if(vector3.IsEmpty()) return false; + return val->IsObject() && val.As()->InstanceOf(GetContext(), vector3).ToChecked(); +} + +bool V8ResourceImpl::IsVector2(v8::Local val) +{ + v8::Local vector2 = bindingHandler->GetBindingExport(js::BindingExport::VECTOR2_CLASS); + if(vector2.IsEmpty()) return false; + return val->IsObject() && val.As()->InstanceOf(GetContext(), vector2).ToChecked(); +} + +bool V8ResourceImpl::IsQuaternion(v8::Local val) +{ + v8::Local quaternion = bindingHandler->GetBindingExport(js::BindingExport::QUATERNION_CLASS); + if(quaternion.IsEmpty()) return false; + return val->IsObject() && val.As()->InstanceOf(GetContext(), quaternion).ToChecked(); +} + +bool V8ResourceImpl::IsRGBA(v8::Local val) +{ + v8::Local rgba = bindingHandler->GetBindingExport(js::BindingExport::RGBA_CLASS); + if(rgba.IsEmpty()) return false; + return val->IsObject() && val.As()->InstanceOf(GetContext(), rgba).ToChecked(); +} + +bool V8ResourceImpl::IsBaseObject(v8::Local val) +{ + return val->IsObject() && val.As()->InstanceOf(GetContext(), baseObjectClass.Get(isolate).As()).ToChecked(); +} void V8ResourceImpl::OnCreateBaseObject(alt::IBaseObject* handle) { @@ -629,15 +630,15 @@ void V8ResourceImpl::InvokeEventHandlers(const alt::CEvent* ev, const std::vecto } } -void V8ResourceImpl::PrintHealth() +void V8ResourceImpl::PrintHealth() { Log::Info << "Resource health: " << resource->GetName() << Log::Endl; Log::Info << " - Entities: " << entities.size() << Log::Endl; Log::Info << " - Timers: " << timers.size() << Log::Endl; - Log::Info << " - Timer benchmarks: " << benchmarkTimers.size() << Log::Endl; - -#ifdef ALT_SERVER_API - Log::Info << " - Vehicle passengers: " << vehiclePassengers.size() << Log::Endl; + Log::Info << " - Timer benchmarks: " << benchmarkTimers.size() << Log::Endl; + +#ifdef ALT_SERVER_API + Log::Info << " - Vehicle passengers: " << vehiclePassengers.size() << Log::Endl; #endif // ALT_SERVER_API } @@ -794,3 +795,72 @@ alt::MValue V8ResourceImpl::FunctionImpl::Call(alt::MValueArgs args) const return res; } + +void V8ResourceImpl::InitializeBinding(js::Binding *binding) +{ + if(binding->IsBootstrapBinding()) return; // Skip bootstrap bindings, those are handled separately + + v8::Local module = binding->GetCompiledModule(this); + if(module.IsEmpty()) + { + Log::Error << "INTERNAL ERROR: Failed to compile binding module " << binding->GetName() << Log::Endl; + return; + } + if(module->GetStatus() == v8::Module::Status::kEvaluated) return; + + if(module->GetStatus() == v8::Module::Status::kEvaluating) + { + Log::Error << "INTERNAL ERROR: Binding module " << binding->GetName() << " is already evaluating; circular dependency?" << Log::Endl; + return; + } + + module->Evaluate(GetContext()); + if(module->GetStatus() != v8::Module::Status::kEvaluated) + { + Log::Error << "INTERNAL ERROR: Failed to evaluate binding module " + binding->GetName() << Log::Endl; + v8::Local exceptionObj = module->GetException().As(); + Log::Error << js::GetV8ObjectValue(context.Get(isolate), exceptionObj, "message") << Log::Endl; + std::string stack = js::GetV8ObjectValue(context.Get(isolate), exceptionObj, "stack"); + if(!stack.empty()) Log::Error << stack << Log::Endl; + } +} + +static void RequireBindingNamespaceWrapper(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + V8_CHECK_ARGS_LEN(1); + + V8_ARG_TO_STRING(1, bindingName); + + js::Binding& binding = js::Binding::Get(bindingName); + if(!binding.IsValid()) + { + Log::Error << "Invalid binding name " << bindingName << Log::Endl; + return; + } + if(!resource) return; + + v8::Local bindingModule = binding.GetCompiledModule(resource); + if(bindingModule->GetStatus() != v8::Module::Status::kEvaluated) resource->InitializeBinding(&binding); + V8_RETURN(bindingModule->GetModuleNamespace()); +} + +void V8ResourceImpl::InitializeBindings(js::Binding::Scope scope, V8Module& altModule) +{ + std::vector bindings = js::Binding::GetBindingsForScope(scope); + v8::Local ctx = GetContext(); + + { + js::TemporaryGlobalExtension altExtension(ctx, "__alt", altModule.GetModuleExports(isolate)); + js::TemporaryGlobalExtension cppBindingsExtension(ctx, "__cppBindings", V8Module::Get("cppBindings").GetModuleExports(isolate)); + js::TemporaryGlobalExtension requireBindingExtension(ctx, "requireBinding", RequireBindingNamespaceWrapper); + + for(js::Binding* binding : bindings) InitializeBinding(binding); + } + + { + // Re-initialize modules, to load new js bindings + V8ResourceImpl::SetBindingsInitialized(true); + V8Module::Initialize(isolate); + } +} \ No newline at end of file diff --git a/shared/V8ResourceImpl.h b/shared/V8ResourceImpl.h index 7650db38..5aa35af7 100644 --- a/shared/V8ResourceImpl.h +++ b/shared/V8ResourceImpl.h @@ -9,9 +9,11 @@ #include "V8Entity.h" #include "V8Timer.h" +#include "V8Module.h" #include "IRuntimeEventHandler.h" #include "V8Helpers.h" +#include "helpers/JSBindings.h" class V8ResourceImpl : public alt::IResource::Impl { @@ -28,7 +30,23 @@ class V8ResourceImpl : public alt::IResource::Impl v8::Global function; }; - V8ResourceImpl(v8::Isolate* _isolate, alt::IResource* _resource) : isolate(_isolate), resource(_resource) {} + V8ResourceImpl(v8::Isolate* _isolate, alt::IResource* _resource) : isolate(_isolate), resource(_resource), bindingHandler(new js::BindingHandler()) {} + + void InitializeBinding(js::Binding* binding); + void InitializeBindings(js::Binding::Scope scope, V8Module& module); + + inline js::BindingHandler* GetBindingHandler() const { return bindingHandler; } + + void SetBindingsInitialized(bool status) { isBindingsInitialized = status; }; + bool IsBindingsInitialized() const { return isBindingsInitialized; }; + + void Initialize() + { + V8Class::Initialize(isolate); + V8Module::Initialize(isolate); + V8ResourceImpl::Start(); + V8ResourceImpl::SetupScriptGlobals(); + } bool Start() override; bool Stop() override; @@ -189,11 +207,11 @@ class V8ResourceImpl : public alt::IResource::Impl v8::Local CreateQuaternion(alt::Quaternion quat); v8::Local CreateRGBA(alt::RGBA rgba); - /*bool IsVector3(v8::Local val); + bool IsVector3(v8::Local val); bool IsVector2(v8::Local val); bool IsQuaternion(v8::Local val); bool IsRGBA(v8::Local val); - bool IsBaseObject(v8::Local val);*/ + bool IsBaseObject(v8::Local val); void OnCreateBaseObject(alt::IBaseObject* handle) override; void OnRemoveBaseObject(alt::IBaseObject* handle) override; @@ -349,6 +367,10 @@ class V8ResourceImpl : public alt::IResource::Impl v8::Isolate* isolate; alt::IResource* resource; + bool isBindingsInitialized = false; + + js::BindingHandler* bindingHandler; + V8Helpers::CPersistent context; std::unordered_map entities; @@ -376,10 +398,6 @@ class V8ResourceImpl : public alt::IResource::Impl bool weaponObjectPoolDirty = true; V8Helpers::CPersistent weaponObjects; - V8Helpers::CPersistent vector3Class; - V8Helpers::CPersistent vector2Class; - V8Helpers::CPersistent quaternionClass; - V8Helpers::CPersistent rgbaClass; V8Helpers::CPersistent baseObjectClass; V8Helpers::CPersistent logFunction; @@ -424,7 +442,7 @@ class V8ResourceImpl : public alt::IResource::Impl ObjectKey AKey{ this, "a" }; ObjectKey PosKey{ this, "pos" }; - ObjectKey WeaponKey{ this, "weapon" }; - + ObjectKey WeaponKey{ this, "weapon" }; + void PrintHealth(); }; diff --git a/shared/bindings/BindingsMain.cpp b/shared/bindings/BindingsMain.cpp index a291be98..909082c7 100644 --- a/shared/bindings/BindingsMain.cpp +++ b/shared/bindings/BindingsMain.cpp @@ -3,7 +3,7 @@ #include "../V8Helpers.h" #include "../V8ResourceImpl.h" #include "../V8Module.h" -#include "../JSEnums.h" +#include "../CompiledEnums.h" static void HashCb(const v8::FunctionCallbackInfo& info) { @@ -474,7 +474,7 @@ static v8::MaybeLocal HandleEnumsModuleImport(v8::Local } static void AddEnumsToSharedModuleExports(v8::Isolate* isolate, v8::Local ctx, v8::Local exports) { - v8::Local sourceCode = V8Helpers::JSValue(JSEnums::GetBindingsCode()); + v8::Local sourceCode = V8Helpers::JSValue(CompiledEnums::GetBindingsCode()); v8::ScriptOrigin scriptOrigin(isolate, V8Helpers::JSValue("js-enums"), 0, 0, false, -1, v8::Local(), false, false, true, v8::Local()); @@ -515,15 +515,16 @@ static void AddEnumsToSharedModuleExports(v8::Isolate* isolate, v8::Local ctx, v8::Local exports) { v8::Isolate* isolate = ctx->GetIsolate(); + V8ResourceImpl* resource = V8ResourceImpl::Get(ctx); V8Helpers::RegisterFunc(exports, "hash", &HashCb); @@ -571,6 +572,14 @@ extern V8Module V8Helpers::RegisterFunc(exports, "getVoiceConnectionState", &GetVoiceConnectionState); V8Helpers::RegisterFunc(exports, "getNetTime", &GetNetTime); + if (resource->IsBindingsInitialized()) + { + V8Helpers::RegisterBindingExport(exports, "Vector2", js::BindingExport::VECTOR2_CLASS); + V8Helpers::RegisterBindingExport(exports, "Vector3", js::BindingExport::VECTOR3_CLASS); + V8Helpers::RegisterBindingExport(exports, "RGBA", js::BindingExport::RGBA_CLASS); + V8Helpers::RegisterBindingExport(exports, "Quaternion", js::BindingExport::QUATERNION_CLASS); + } + V8_OBJECT_SET_STRING(exports, "version", alt::ICore::Instance().GetVersion()); V8_OBJECT_SET_STRING(exports, "branch", alt::ICore::Instance().GetBranch()); // V8_OBJECT_SET_RAW_STRING(exports, "sdkVersion", ALT_SDK_VERSION); diff --git a/shared/bindings/Quaternion.cpp b/shared/bindings/Quaternion.cpp deleted file mode 100644 index 7b9e5eba..00000000 --- a/shared/bindings/Quaternion.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include - -#include "../V8Class.h" -#include "../V8Helpers.h" -#include "../V8ResourceImpl.h" - -static void Constructor(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_CONSTRUCTOR(); - V8_CHECK_ARGS_LEN2(1, 4); - - v8::Local _this = info.This(); - V8Helpers::SetObjectClass(isolate, _this, V8Class::ObjectClass::QUATERNION); - - v8::Local x, y, z, w; - - if(info.Length() == 1) - { - v8::Local val = info[0]; - - if(info[0]->IsArray()) - { - v8::Local arr = val.As(); - V8_CHECK(arr->Length() == 4, "Array has to contain 4 (X, Y, Z, W) values"); - - x = arr->Get(ctx, 0).ToLocalChecked(); - y = arr->Get(ctx, 1).ToLocalChecked(); - z = arr->Get(ctx, 2).ToLocalChecked(); - w = arr->Get(ctx, 3).ToLocalChecked(); - - V8_CHECK(x->IsNumber(), "Argument must be an array of 4 numbers"); - V8_CHECK(y->IsNumber(), "Argument must be an array of 4 numbers"); - V8_CHECK(z->IsNumber(), "Argument must be an array of 4 numbers"); - V8_CHECK(w->IsNumber(), "Argument must be an array of 4 numbers"); - } - else if(info[1]->IsObject()) - { - v8::Local obj = val.As(); - - x = obj->Get(ctx, resource->XKey()).ToLocalChecked(); - y = obj->Get(ctx, resource->YKey()).ToLocalChecked(); - z = obj->Get(ctx, resource->ZKey()).ToLocalChecked(); - w = obj->Get(ctx, resource->WKey()).ToLocalChecked(); - - V8_CHECK(x->IsNumber(), "Argument must be an array of 4 numbers"); - V8_CHECK(y->IsNumber(), "Argument must be an array of 4 numbers"); - V8_CHECK(z->IsNumber(), "Argument must be an array of 4 numbers"); - V8_CHECK(w->IsNumber(), "Argument must be an array of 4 numbers"); - } - else if(val->IsNumber()) - { - x = val; - y = val; - z = val; - w = val; - } - else - { - V8Helpers::Throw(isolate, "Invalid arguments"); - return; - } - } - else - { - V8_ARG_CHECK_NUMBER(1); - V8_ARG_CHECK_NUMBER(2); - V8_ARG_CHECK_NUMBER(3); - V8_ARG_CHECK_NUMBER(4); - - x = info[0]; - y = info[1]; - z = info[2]; - w = info[3]; - } - - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->XKey(), x, v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->YKey(), y, v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->ZKey(), z, v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->WKey(), w, v8::PropertyAttribute::ReadOnly); -} - -extern V8Class v8Quaternion("Quaternion", - Constructor, - [](v8::Local tpl) - { - tpl->InstanceTemplate()->SetInternalFieldCount(static_cast(V8Class::InternalFields::COUNT)); - }); diff --git a/shared/bindings/Quaternion.js b/shared/bindings/Quaternion.js deleted file mode 100644 index aa6643eb..00000000 --- a/shared/bindings/Quaternion.js +++ /dev/null @@ -1,97 +0,0 @@ -/// -// clang-format off -// Quaternion JS bindings - -/* - * Can be used if someone wants to add .add, .sub, .div etc. -function getXYZWFromArgs(args) { - alt.Utils.assert(args.length === 1 || args.length === 4, "1 or 4 arguments expected"); - - const firstArg = args[0]; - let x = 0, y = 0, z = 0, w = 0; - - if(args.length === 4) { - x = parseFloat(firstArg); - y = parseFloat(args[1]); - z = parseFloat(args[2]); - w = parseFloat(args[3]); - - assertNotNaN(x, "Expected a number as first argument"); - assertNotNaN(y, "Expected a number as second argument"); - assertNotNaN(z, "Expected a number as third argument"); - assertNotNaN(w, "Expected a number as fourth argument"); - } - else { - if(typeof firstArg === "number" || typeof firstArg === "string") { - const number = parseFloat(firstArg); - assertNotNaN(number, "Expected a number or string as first argument"); - - x = number; - y = number; - z = number; - w = number; - } - else if(Array.isArray(firstArg)) { - if(typeof firstArg[0] === "number" || typeof firstArg[0] === "string") { - x = parseFloat(firstArg[0]); - assertNotNaN(x, "Expected an array of 4 numbers as first argument"); - } - if(typeof firstArg[1] === "number" || typeof firstArg[1] === "string") { - y = parseFloat(firstArg[1]); - assertNotNaN(y, "Expected an array of 4 numbers as first argument"); - } - if(typeof firstArg[2] === "number" || typeof firstArg[2] === "string") { - z = parseFloat(firstArg[2]); - assertNotNaN(z, "Expected an array of 4 numbers as first argument"); - } - if(typeof firstArg[3] === "number" || typeof firstArg[3] === "string") { - w = parseFloat(firstArg[3]); - assertNotNaN(w, "Expected an array of 4 numbers as first argument"); - } - } - else if(firstArg && typeof firstArg === "object") { - if(firstArg.x !== undefined) { - x = parseFloat(firstArg.x); - assertNotNaN(x, "Expected Quaternion as first argument"); - } - if(firstArg.y !== undefined) { - y = parseFloat(firstArg.y); - assertNotNaN(y, "Expected Quaternion as first argument"); - } - if(firstArg.z !== undefined) { - z = parseFloat(firstArg.z); - assertNotNaN(z, "Expected Quaternion as first argument"); - } - if(firstArg.w !== undefined) { - w = parseFloat(firstArg.w); - assertNotNaN(w, "Expected Quaternion as first argument"); - } - } - else throw new Error("Argument must be a number, an array of 4 numbers or IQuaternion"); - } - - return [x, y, z, w]; -} - */ - -// Static properties -alt.Quaternion.zero = new alt.Quaternion(0, 0, 0, 0); -alt.Quaternion.one = new alt.Quaternion(1, 1, 1, 1); - -// Instance methods -alt.Quaternion.prototype.toString = function() { - return `Quaternion{ x: ${this.x.toFixed(4)}, y: ${this.y.toFixed(4)}, z: ${this.z.toFixed(4)}, w: ${this.w.toFixed(4)} }`; -} - -alt.Quaternion.prototype.toArray = function() { - return [this.x, this.y, this.z, this.w]; -} - -alt.Quaternion.prototype.toFixed = function(precision = 4) { - return new alt.Quaternion( - parseFloat(this.x.toFixed(precision)), - parseFloat(this.y.toFixed(precision)), - parseFloat(this.z.toFixed(precision)), - parseFloat(this.w.toFixed(precision)), - ); -} diff --git a/shared/bindings/RGBA.cpp b/shared/bindings/RGBA.cpp deleted file mode 100644 index cc811edb..00000000 --- a/shared/bindings/RGBA.cpp +++ /dev/null @@ -1,75 +0,0 @@ - -#include "../V8Class.h" -#include "../V8Helpers.h" -#include "../V8ResourceImpl.h" - -static void Constructor(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN_MIN_MAX(1, 4); - - V8Helpers::SetObjectClass(isolate, info.This(), V8Class::ObjectClass::RGBA); - int32_t r = 0, g = 0, b = 0, a = 255; - - if(info.Length() == 1) - { - if(info[0]->IsArray()) - { - v8::Local arr = info[0].As(); - V8_CHECK(arr->Length() >= 3, "Array has to contain atleast 3 (R, G, B) values"); - std::optional> values = V8Helpers::CppValue(arr); - V8_CHECK(values.has_value(), "Invalid array passed"); - r = values->at(0); - g = values->at(1); - b = values->at(2); - if(values->size() > 3) a = values->at(3); - } - else if(info[0]->IsObject()) - { - v8::Local obj = info[0].As(); - std::optional> values = V8Helpers::CppValue(obj); - V8_CHECK(values.has_value(), "Invalid object passed"); - r = values->at("r"); - g = values->at("g"); - b = values->at("b"); - if(values->contains("a")) a = values->at("a"); - } - else - { - V8Helpers::Throw(isolate, "Invalid arguments"); - return; - } - } - else if(info.Length() >= 3) - { - V8_ARG_TO_INT32(1, red); - V8_ARG_TO_INT32(2, green); - V8_ARG_TO_INT32(3, blue); - r = red; - g = green; - b = blue; - } - if(info.Length() == 4) - { - V8_ARG_TO_INT32(4, alpha); - a = alpha; - } - - V8_CHECK(r >= 0 && r < 256, "Invalid RGBA R value. Allowed is 0 - 255"); - V8_CHECK(g >= 0 && g < 256, "Invalid RGBA G value. Allowed is 0 - 255"); - V8_CHECK(b >= 0 && b < 256, "Invalid RGBA B value. Allowed is 0 - 255"); - V8_CHECK(a >= 0 && a < 256, "Invalid RGBA A value. Allowed is 0 - 255"); - - V8Helpers::DefineOwnProperty(isolate, ctx, info.This(), resource->RKey(), V8Helpers::JSValue(r), v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, info.This(), resource->GKey(), V8Helpers::JSValue(g), v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, info.This(), resource->BKey(), V8Helpers::JSValue(b), v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, info.This(), resource->AKey(), V8Helpers::JSValue(a), v8::PropertyAttribute::ReadOnly); -} - -extern V8Class v8RGBA("RGBA", - &Constructor, - [](v8::Local tpl) - { - tpl->InstanceTemplate()->SetInternalFieldCount(static_cast(V8Class::InternalFields::COUNT)); - }); diff --git a/shared/bindings/RGBA.js b/shared/bindings/RGBA.js deleted file mode 100644 index 2b2863bb..00000000 --- a/shared/bindings/RGBA.js +++ /dev/null @@ -1,36 +0,0 @@ -/// -// clang-format off -// RGBA JS bindings - -// Static properties -alt.RGBA.red = new alt.RGBA(255, 0, 0, 255); -alt.RGBA.green = new alt.RGBA(0, 255, 0, 255); -alt.RGBA.blue = new alt.RGBA(0, 0, 255, 255); -alt.RGBA.black = new alt.RGBA(0, 0, 0, 255); -alt.RGBA.white = new alt.RGBA(255, 255, 255, 255); -alt.RGBA.clear = new alt.RGBA(0, 0, 0, 0); - -// Instance methods -alt.RGBA.prototype.toString = function() { - return `RGBA{ r: ${this.r}, g: ${this.g}, b: ${this.b}, a: ${this.a} }`; -} - -alt.RGBA.prototype.toBGRA = function() { - return new alt.RGBA(this.b, this.g, this.r, this.a); -} - -alt.RGBA.prototype.toARGB = function() { - return new alt.RGBA(this.a, this.r, this.g, this.b); -} - -alt.RGBA.prototype.toInt = function() { - let int = this.r << 24; - int |= this.g << 16; - int |= this.b << 8; - int |= this.a; - return int; -} - -alt.RGBA.prototype.toArray = function() { - return [this.r, this.g, this.b, this.a]; -} diff --git a/shared/bindings/Resource.cpp b/shared/bindings/Resource.cpp index 3837f1f1..23e85097 100644 --- a/shared/bindings/Resource.cpp +++ b/shared/bindings/Resource.cpp @@ -176,15 +176,15 @@ extern V8Class v8Resource("Resource", tpl->InstanceTemplate()->SetInternalFieldCount(static_cast(V8Class::InternalFields::COUNT)); - V8Helpers::SetAccessor(isolate, tpl, "isStarted", &IsStartedGetter); - V8Helpers::SetAccessor(isolate, tpl, "type", &TypeGetter); - V8Helpers::SetAccessor(isolate, tpl, "name", &NameGetter); - V8Helpers::SetAccessor(isolate, tpl, "main", &MainGetter); - V8Helpers::SetAccessor(isolate, tpl, "exports", &ExportsGetter); - V8Helpers::SetAccessor(isolate, tpl, "dependencies", &DependenciesGetter); - V8Helpers::SetAccessor(isolate, tpl, "dependants", &DependantsGetter); - V8Helpers::SetAccessor(isolate, tpl, "requiredPermissions", &RequiredPermissionsGetter); - V8Helpers::SetAccessor(isolate, tpl, "optionalPermissions", &OptionalPermissionsGetter); + V8Helpers::SetAccessor(isolate, tpl, "isStarted", &IsStartedGetter); + V8Helpers::SetAccessor(isolate, tpl, "type", &TypeGetter); + V8Helpers::SetAccessor(isolate, tpl, "name", &NameGetter); + V8Helpers::SetAccessor(isolate, tpl, "main", &MainGetter); + V8Helpers::SetAccessor(isolate, tpl, "exports", &ExportsGetter); + V8Helpers::SetAccessor(isolate, tpl, "dependencies", &DependenciesGetter); + V8Helpers::SetAccessor(isolate, tpl, "dependants", &DependantsGetter); + V8Helpers::SetAccessor(isolate, tpl, "requiredPermissions", &RequiredPermissionsGetter); + V8Helpers::SetAccessor(isolate, tpl, "optionalPermissions", &OptionalPermissionsGetter); V8Helpers::SetAccessor(isolate, tpl, "valid", &ValidGetter); #ifdef ALT_SERVER_API V8Helpers::SetAccessor(isolate, tpl, "path", &PathGetter); diff --git a/shared/bindings/SharedCppBindings.cpp b/shared/bindings/SharedCppBindings.cpp new file mode 100644 index 00000000..95929830 --- /dev/null +++ b/shared/bindings/SharedCppBindings.cpp @@ -0,0 +1,49 @@ +#include "../V8Helpers.h" +#include "../V8ResourceImpl.h" +#include "../V8Module.h" + +static void RegisterExport(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + V8_CHECK_ARGS_LEN(2); + + V8_ARG_TO_NUMBER(1, export_); + V8_ARG_TO_OBJECT(2, value); + + if (resource->GetBindingHandler()->HasBindingExport(static_cast(export_))) return; + resource->GetBindingHandler()->SetBindingExport(static_cast(export_), value); +} + +static void GetBuiltinModule(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_CHECK_ARGS_LEN(1); + V8_ARG_TO_STRING(1, name); + + auto altInternalMap = std::map({ + { "alt-client", "alt" }, + { "alt-server", "alt" }, + }); + if (altInternalMap.contains(name)) name = altInternalMap.at(name); + + if(!V8Module::Exists(name)) + { + V8_RETURN_NULL(); + return; + } + V8Module& mod = V8Module::Get(name); + V8_RETURN(mod.GetModuleExports(isolate)); +} + +extern V8Module + v8CppBindings("cppBindings", + nullptr, + {}, + [](v8::Local ctx, v8::Local exports) + { + V8Helpers::RegisterFunc(exports, "registerExport", &RegisterExport); + V8Helpers::RegisterFunc(exports, "getBuiltinModule", &GetBuiltinModule); + + V8Helpers::RegisterEnum(exports, "BindingExport"); + }); \ No newline at end of file diff --git a/shared/bindings/Utils.cpp b/shared/bindings/Utils.cpp index 9334129f..ea24efeb 100644 --- a/shared/bindings/Utils.cpp +++ b/shared/bindings/Utils.cpp @@ -1,4 +1,65 @@ +#include +#include + #include "../V8Class.h" -// Bindings are added here from JS -extern V8Class v8Utils("Utils", [](v8::Local tpl) {}); +static void IsVector2(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_CHECK_ARGS_LEN(1); + V8_ARG_TO_OBJECT(1, vector2); + + V8_RETURN(resource->IsVector2(vector2)); +} + +static void IsVector3(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_CHECK_ARGS_LEN(1); + V8_ARG_TO_OBJECT(1, vector3); + + V8_RETURN(resource->IsVector3(vector3)); +} + +static void IsRGBA(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_CHECK_ARGS_LEN(1); + V8_ARG_TO_OBJECT(1, rgba); + + V8_RETURN(resource->IsRGBA(rgba)); +} + +static void IsQuaternion(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_CHECK_ARGS_LEN(1); + V8_ARG_TO_OBJECT(1, quaternion); + + V8_RETURN(resource->IsQuaternion(quaternion)); +} + +static void IsBaseObject(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_CHECK_ARGS_LEN(1); + V8_ARG_TO_OBJECT(1, baseValue); + + V8_RETURN(resource->IsBaseObject(baseValue)); +} + +extern V8Class v8Utils("Utils", [](v8::Local tpl) +{ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + + V8Helpers::SetStaticMethod(isolate, tpl, "isVector2", IsVector2); + V8Helpers::SetStaticMethod(isolate, tpl, "isVector3", IsVector3); + V8Helpers::SetStaticMethod(isolate, tpl, "isRGBA", IsRGBA); + V8Helpers::SetStaticMethod(isolate, tpl, "isQuaternion", IsQuaternion); + V8Helpers::SetStaticMethod(isolate, tpl, "isBaseObject", IsBaseObject); +}); diff --git a/shared/bindings/Vector2.cpp b/shared/bindings/Vector2.cpp deleted file mode 100644 index 4ca5095f..00000000 --- a/shared/bindings/Vector2.cpp +++ /dev/null @@ -1,565 +0,0 @@ -#include - -#include "../V8Class.h" -#include "../V8Helpers.h" -#include "../V8ResourceImpl.h" - -constexpr double PI = 3.141592653589793238463; - -static void ToString(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - std::ostringstream ss; - ss << std::fixed << std::setprecision(4) << "Vector2{ x: " << x << ", y: " << y << " }"; - - V8_RETURN_STRING(ss.str()); -} - -static void ToArray(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - v8::Local arr = v8::Array::New(isolate, 2); - arr->Set(ctx, 0, V8Helpers::JSValue(x)); - arr->Set(ctx, 1, V8Helpers::JSValue(y)); - - V8_RETURN(arr); -} - -static void Length(v8::Local, const v8::PropertyCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - double length = sqrt(x * x + y * y); - - V8_RETURN_NUMBER(length); -} - -static void Add(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN2(1, 2); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - if(info.Length() == 2) - { - V8_ARG_TO_NUMBER(1, x2); - V8_ARG_TO_NUMBER(2, y2); - - V8_RETURN(resource->CreateVector2({ x + x2, y + y2 })); - } - else if(info.Length() == 1) - { - auto arg = info[0]; - if(arg->IsNumber()) - { - V8_ARG_TO_NUMBER(1, value); - V8_RETURN(resource->CreateVector2({ x + value, y + value })); - } - else if(arg->IsArray()) - { - v8::Local arr = arg.As(); - V8_CHECK(arr->Length() == 2, "Argument must be an array of 2 numbers"); - - V8_TO_NUMBER(arr->Get(ctx, 0).ToLocalChecked(), x2); - V8_TO_NUMBER(arr->Get(ctx, 1).ToLocalChecked(), y2); - V8_RETURN(resource->CreateVector2({ x + x2, y + y2 })); - } - else if(arg->IsObject()) - { - v8::Local obj = arg.As(); - - V8_TO_NUMBER(obj->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(obj->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - V8_RETURN(resource->CreateVector2({ x + x2, y + y2 })); - } - else - { - V8Helpers::Throw(isolate, "Argument must be a number, an array of 2 numbers or IVector2"); - } - } -} - -static void Sub(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN2(1, 2); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - if(info.Length() == 2) - { - V8_ARG_TO_NUMBER(1, x2); - V8_ARG_TO_NUMBER(2, y2); - - V8_RETURN(resource->CreateVector2({ x - x2, y - y2 })); - } - else if(info.Length() == 1) - { - auto arg = info[0]; - if(arg->IsNumber()) - { - V8_ARG_TO_NUMBER(1, value); - V8_RETURN(resource->CreateVector2({ x - value, y - value })); - } - else if(arg->IsArray()) - { - v8::Local arr = arg.As(); - V8_CHECK(arr->Length() == 2, "Argument must be an array of 2 numbers"); - - V8_TO_NUMBER(arr->Get(ctx, 0).ToLocalChecked(), x2); - V8_TO_NUMBER(arr->Get(ctx, 1).ToLocalChecked(), y2); - V8_RETURN(resource->CreateVector2({ x - x2, y - y2 })); - } - else if(arg->IsObject()) - { - v8::Local obj = arg.As(); - - V8_TO_NUMBER(obj->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(obj->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - V8_RETURN(resource->CreateVector2({ x - x2, y - y2 })); - } - else - { - V8Helpers::Throw(isolate, "Argument must be a number, an array of 2 numbers or IVector2"); - } - } -} - -static void Divide(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN2(1, 2); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - if(info.Length() == 2) - { - V8_ARG_TO_NUMBER(1, x2); - V8_ARG_TO_NUMBER(2, y2); - - V8_CHECK(x2 != 0 && y2 != 0, "Division by zero"); - V8_RETURN(resource->CreateVector2({ x / x2, y / y2 })); - } - else if(info.Length() == 1) - { - auto arg = info[0]; - if(arg->IsNumber()) - { - V8_ARG_TO_NUMBER(1, value); - V8_CHECK(value != 0, "Division by zero"); - V8_RETURN(resource->CreateVector2({ x / value, y / value })); - } - else if(arg->IsArray()) - { - v8::Local arr = arg.As(); - V8_CHECK(arr->Length() == 2, "Argument must be an array of 2 numbers"); - - V8_TO_NUMBER(arr->Get(ctx, 0).ToLocalChecked(), x2); - V8_TO_NUMBER(arr->Get(ctx, 1).ToLocalChecked(), y2); - V8_CHECK(x2 != 0 && y2 != 0, "Division by zero"); - V8_RETURN(resource->CreateVector2({ x / x2, y / y2 })); - } - else if(arg->IsObject()) - { - v8::Local obj = arg.As(); - - V8_TO_NUMBER(obj->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(obj->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - V8_CHECK(x2 != 0 && y2 != 0, "Division by zero"); - V8_RETURN(resource->CreateVector2({ x / x2, y / y2 })); - } - else - { - V8Helpers::Throw(isolate, "Argument must be a number, an array of 2 numbers or IVector2"); - } - } -} - -static void Multiply(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN2(1, 2); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - if(info.Length() == 2) - { - V8_ARG_TO_NUMBER(1, x2); - V8_ARG_TO_NUMBER(2, y2); - - V8_RETURN(resource->CreateVector2({ x * x2, y * y2 })); - } - else if(info.Length() == 1) - { - auto arg = info[0]; - if(arg->IsNumber()) - { - V8_ARG_TO_NUMBER(1, value); - V8_RETURN(resource->CreateVector2({ x * value, y * value })); - } - else if(arg->IsArray()) - { - v8::Local arr = arg.As(); - V8_CHECK(arr->Length() == 2, "Argument must be an array of 2 numbers"); - - V8_TO_NUMBER(arr->Get(ctx, 0).ToLocalChecked(), x2); - V8_TO_NUMBER(arr->Get(ctx, 1).ToLocalChecked(), y2); - V8_RETURN(resource->CreateVector2({ x * x2, y * y2 })); - } - else if(arg->IsObject()) - { - v8::Local obj = arg.As(); - - V8_TO_NUMBER(obj->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(obj->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - V8_RETURN(resource->CreateVector2({ x * x2, y * y2 })); - } - else - { - V8Helpers::Throw(isolate, "Argument must be a number, an array of 2 numbers or IVector2"); - } - } -} - -static void Dot(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN2(1, 2); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - if(info.Length() == 2) - { - V8_ARG_TO_NUMBER(1, x2); - V8_ARG_TO_NUMBER(2, y2); - - V8_RETURN_NUMBER(x * x2 + y * y2); - } - else if(info.Length() == 1) - { - auto arg = info[0]; - if(arg->IsNumber()) - { - V8_ARG_TO_NUMBER(1, value); - - V8_RETURN_NUMBER(x * value + y * value); - } - else if(arg->IsArray()) - { - v8::Local arr = arg.As(); - V8_CHECK(arr->Length() == 2, "Argument must be an array of 2 numbers"); - - V8_TO_NUMBER(arr->Get(ctx, 0).ToLocalChecked(), x2); - V8_TO_NUMBER(arr->Get(ctx, 1).ToLocalChecked(), y2); - - V8_RETURN_NUMBER(x * x2 + y * y2); - } - else if(arg->IsObject()) - { - v8::Local obj = arg.As(); - - V8_TO_NUMBER(obj->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(obj->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - V8_RETURN_NUMBER(x * x2 + y * y2); - } - else - { - V8Helpers::Throw(isolate, "Argument must be a number, an array of 2 numbers or IVector2"); - } - } -} - -static void Negative(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - V8_RETURN(resource->CreateVector2({ -x, -y })); -} - -static void Normalize(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - double length = sqrt(x * x + y * y); - - V8_RETURN(resource->CreateVector2({ x / length, y / length })); -} - -static void DistanceTo(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN(1); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - V8_ARG_TO_OBJECT(1, vec); - - V8_TO_NUMBER(vec->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(vec->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - double xFinal = x - x2; - double yFinal = y - y2; - double dist = sqrt((xFinal * xFinal) + (yFinal * yFinal)); - - V8_RETURN_NUMBER(dist); -} - -static void AngleTo(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN(1); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - V8_ARG_TO_OBJECT(1, vec); - - V8_TO_NUMBER(vec->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(vec->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - double xy = x * x2 + y * y2; - double posALength = sqrt(std::pow(x, 2) + std::pow(y, 2)); - double posBLength = sqrt(std::pow(x2, 2) + std::pow(y2, 2)); - - if(posALength == 0 || posBLength == 0) - { - V8Helpers::Throw(isolate, "Division by zero!"); - return; - } - - double cos = xy / (posALength * posBLength); - double radians = std::acos(cos); - // double angle = radians * (180 / PI); - - V8_RETURN_NUMBER(radians); -} - -static void AngleToDegrees(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN(1); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - V8_ARG_TO_OBJECT(1, vec); - - V8_TO_NUMBER(vec->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(vec->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - double xy = x * x2 + y * y2; - double posALength = sqrt(std::pow(x, 2) + std::pow(y, 2)); - double posBLength = sqrt(std::pow(x2, 2) + std::pow(y2, 2)); - - if(posALength == 0 || posBLength == 0) - { - V8Helpers::Throw(isolate, "Division by zero!"); - return; - } - - double cos = xy / (posALength * posBLength); - double radians = std::acos(cos); - double angle = radians * (180 / PI); - - V8_RETURN_NUMBER(angle); -} - -static void ToDegrees(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - double x2 = (x * 180) / PI; - double y2 = (y * 180) / PI; - - V8_RETURN(resource->CreateVector2({ x2, y2 })); -} - -static void ToRadians(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - double x2 = (x * PI) / 180; - double y2 = (y * PI) / 180; - - V8_RETURN(resource->CreateVector2({ x2, y2 })); -} - -static void IsInRange(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN(2); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->XKey()), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, resource->YKey()), y); - - V8_ARG_TO_OBJECT(1, vec); - V8_ARG_TO_NUMBER(2, range); - - V8_TO_NUMBER(vec->Get(ctx, resource->XKey()).ToLocalChecked(), x2); - V8_TO_NUMBER(vec->Get(ctx, resource->YKey()).ToLocalChecked(), y2); - - double dx = abs(x - x2); - double dy = abs(y - y2); - - bool isInRange = dx <= range && // perform fast check first - dy <= range && dx * dx + dy * dy <= range * range; // perform exact check - - V8_RETURN_BOOLEAN(isInRange); -} - -static void Lerp(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN(2); - - V8_TO_VECTOR2(info.This(), _this); - V8_ARG_TO_VECTOR2(1, vec); - V8_ARG_TO_NUMBER(2, ratio); - - constexpr auto lerp = [](float a, float b, float t) { return a + (b - a) * t; }; - auto lerpedX = lerp(_this[0], vec[0], (float)ratio); - auto lerpedY = lerp(_this[1], vec[1], (float)ratio); - alt::Vector2f lerpedVector = { lerpedX, lerpedY }; - - V8_RETURN_VECTOR2(lerpedVector); -} - -static void Constructor(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_CONSTRUCTOR(); - V8_CHECK_ARGS_LEN2(1, 2); - - v8::Local _this = info.This(); - V8Helpers::SetObjectClass(isolate, _this, V8Class::ObjectClass::VECTOR2); - - v8::Local x, y; - - if(info.Length() == 2) - { - V8_ARG_CHECK_NUMBER(1); - V8_ARG_CHECK_NUMBER(2); - - x = info[0]; - y = info[1]; - } - else - { - v8::Local val = info[0]; - - if(val->IsArray()) - { - v8::Local arr = val.As(); - V8_CHECK(arr->Length() == 2, "Argument must be an array of 2 numbers"); - - x = arr->Get(ctx, 0).ToLocalChecked(); - y = arr->Get(ctx, 1).ToLocalChecked(); - - V8_CHECK(x->IsNumber(), "Argument must be an array of 2 numbers"); - V8_CHECK(y->IsNumber(), "Argument must be an array of 2 numbers"); - } - else if(val->IsObject()) - { - v8::Local obj = val.As(); - - x = obj->Get(ctx, resource->XKey()).ToLocalChecked(); - y = obj->Get(ctx, resource->YKey()).ToLocalChecked(); - - V8_CHECK(x->IsNumber(), "x must be a number"); - V8_CHECK(y->IsNumber(), "y must be a number"); - } - else if(val->IsNumber()) - { - x = val; - y = val; - } - else - { - V8Helpers::Throw(isolate, "Argument must be an array of 2 numbers or IVector2"); - return; - } - } - - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->XKey(), x, v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->YKey(), y, v8::PropertyAttribute::ReadOnly); -} - -extern V8Class v8Vector2("Vector2", - Constructor, - [](v8::Local tpl) - { - tpl->InstanceTemplate()->SetInternalFieldCount(static_cast(V8Class::InternalFields::COUNT)); - }); diff --git a/shared/bindings/Vector2.js b/shared/bindings/Vector2.js deleted file mode 100644 index 4be2b62b..00000000 --- a/shared/bindings/Vector2.js +++ /dev/null @@ -1,168 +0,0 @@ -/// -// clang-format off -// Vector2 JS bindings - -function getXYFromArgs(args) { - alt.Utils.assert(args.length === 1 || args.length === 2, "1 or 2 arguments expected"); - - let x = 0, y = 0; - - if(args.length === 2) { - x = parseFloat(args[0]); - y = parseFloat(args[1]); - - assertNotNaN(x, "Expected a number as first argument"); - assertNotNaN(y, "Expected a number as second argument"); - } - else { - if(typeof args[0] === "number" || typeof args[0] === "string") { - const number = parseFloat(args[0]); - assertNotNaN(number, "Expected a number as first argument"); - - x = number; - y = number; - } - else if(Array.isArray(args[0])) { - if(typeof args[0][0] === "number" || typeof args[0][0] === "string") { - x = parseFloat(args[0][0]); - assertNotNaN(x, "Expected an array of 2 numbers as first argument"); - } - if(typeof args[0][1] === "number" || typeof args[0][1] === "string") { - y = parseFloat(args[0][1]); - assertNotNaN(y, "Expected an array of 2 numbers as first argument"); - } - } - else if(args[0] && typeof args[0] === "object") { - if(args[0].x !== undefined) { - x = parseFloat(args[0].x); - assertNotNaN(x, "Expected Vector2 as first argument"); - } - if(args[0].y !== undefined) { - y = parseFloat(args[0].y); - assertNotNaN(y, "Expected Vector2 as first argument"); - } - } - else throw new Error("Argument must be a number, an array of 2 numbers or IVector2"); - } - - return [x, y]; -} - -// Static properties -alt.Vector2.zero = new alt.Vector2(0, 0); -alt.Vector2.one = new alt.Vector2(1, 1); -alt.Vector2.up = new alt.Vector2(0, 1); -alt.Vector2.down = new alt.Vector2(0, -1); -alt.Vector2.left = new alt.Vector2(-1, 0); -alt.Vector2.right = new alt.Vector2(1, 0); -alt.Vector2.negativeInfinity = new alt.Vector2(-Infinity, -Infinity); -alt.Vector2.positiveInfinity = new alt.Vector2(Infinity, Infinity); - -// Static getters -Object.defineProperty(alt.Vector2.prototype, "length", { - get: function() { - return Math.sqrt(this.x * this.x + this.y * this.y); - } -}); - -// Instance methods -alt.Vector2.prototype.toString = function() { - return `Vector2{ x: ${this.x.toFixed(4)}, y: ${this.y.toFixed(4)} }`; -} - -alt.Vector2.prototype.toArray = function() { - return [this.x, this.y]; -} - -alt.Vector2.prototype.toFixed = function (precision = 4) { - return new alt.Vector2( - parseFloat(this.x.toFixed(precision)), - parseFloat(this.y.toFixed(precision)), - ); -}; - -alt.Vector2.prototype.add = function(...args) { - const [x, y] = getXYFromArgs(args); - return new alt.Vector2(this.x + x, this.y + y); -} - -alt.Vector2.prototype.sub = function(...args) { - const [x, y] = getXYFromArgs(args); - return new alt.Vector2(this.x - x, this.y - y); -} - -alt.Vector2.prototype.div = function(...args) { - const [x, y] = getXYFromArgs(args); - return new alt.Vector2(this.x / x, this.y / y); -} - -alt.Vector2.prototype.mul = function(...args) { - const [x, y] = getXYFromArgs(args); - return new alt.Vector2(this.x * x, this.y * y); -} - -alt.Vector2.prototype.negative = function() { - return new alt.Vector2(-this.x, -this.y); -} - -alt.Vector2.prototype.inverse = function() { - return new alt.Vector2(1 / this.x, 1 / this.y); -} - -alt.Vector2.prototype.normalize = function() { - const length = this.length; - return new alt.Vector2(this.x == 0 ? 0 : this.x / length, this.y == 0 ? 0 : this.y / length); -} - -alt.Vector2.prototype.distanceTo = function(vector) { - return Math.sqrt(this.distanceToSquared(vector)); -} - -alt.Vector2.prototype.distanceToSquared = function(vector) { - alt.Utils.assert(vector != null, "Expected Vector2 as first argument"); - - const x = this.x - parseFloat(vector.x); - const y = this.y - parseFloat(vector.y); - return x * x + y * y; -} - -alt.Vector2.prototype.angleTo = function(vector) { - alt.Utils.assert(vector != null, "Expected Vector2 as first argument"); - - const posALength = Math.hypot(this.x, this.y); - const posBLength = Math.hypot(vector.x, vector.y); - if (posALength === 0 || posBLength === 0) throw new Error("Division by zero"); - return Math.acos((this.x * vector.x + this.y * vector.y) / (posALength * posBLength)); -} - -alt.Vector2.prototype.angleToDegrees = function(vector) { - return this.angleTo(vector) * (180 / Math.PI); -} - -alt.Vector2.prototype.toDegrees = function() { - return new alt.Vector2((this.x * 180) / Math.PI, (this.y * 180) / Math.PI); -} - -alt.Vector2.prototype.toRadians = function() { - return new alt.Vector2((this.x * Math.PI) / 180, (this.y * Math.PI) / 180); -} - -alt.Vector2.prototype.isInRange = function(vector, range) { - alt.Utils.assert(vector != null, "Expected Vector2 as first argument"); - alt.Utils.assert(typeof range === "number", "Expected a number as second argument"); - - const x = Math.abs(this.x - parseFloat(vector.x)); - const y = Math.abs(this.y - parseFloat(vector.y)); - - return x <= range && y <= range // Fast check - && x * x + y * y <= range * range; // Slow check -} - -alt.Vector2.prototype.lerp = function(vector, ratio) { - alt.Utils.assert(vector != null, "Expected Vector2 as first argument"); - alt.Utils.assert(typeof ratio === "number", "Expected a number as second argument"); - - const x = this.x + (vector.x - this.x) * ratio; - const y = this.y + (vector.y - this.y) * ratio; - return new alt.Vector2(x, y); -} diff --git a/shared/bindings/Vector3.cpp b/shared/bindings/Vector3.cpp deleted file mode 100644 index dffb8a03..00000000 --- a/shared/bindings/Vector3.cpp +++ /dev/null @@ -1,154 +0,0 @@ - -#include - -#include "../V8Class.h" -#include "../V8Helpers.h" -#include "../V8ResourceImpl.h" - -constexpr double PI = 3.141592653589793238463; - -/* -static void AngleTo(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN(1); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, V8Helpers::Vector3_XKey(isolate)), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, V8Helpers::Vector3_YKey(isolate)), y); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, V8Helpers::Vector3_ZKey(isolate)), z); - - V8_ARG_TO_OBJECT(1, vec); - - V8_TO_NUMBER(vec->Get(ctx, V8Helpers::Vector3_XKey(isolate)).ToLocalChecked(), x2); - V8_TO_NUMBER(vec->Get(ctx, V8Helpers::Vector3_YKey(isolate)).ToLocalChecked(), y2); - V8_TO_NUMBER(vec->Get(ctx, V8Helpers::Vector3_ZKey(isolate)).ToLocalChecked(), z2); - - double xy = x * x2 + y * y2; - double posALength = sqrt(std::pow(x, 2) + std::pow(y, 2)); - double posBLength = sqrt(std::pow(x2, 2) + std::pow(y2, 2)); - - if(posALength == 0 || posBLength == 0) - { - V8Helpers::Throw(isolate, "Division by zero!"); - return; - } - - double cos = xy / (posALength * posBLength); - double radians = std::acos(cos); - // double angle = radians * (180 / PI); - - V8_RETURN_NUMBER(radians); -} - -static void AngleToDegrees(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN(1); - - v8::Local _this = info.This(); - - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, V8Helpers::Vector3_XKey(isolate)), x); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, V8Helpers::Vector3_YKey(isolate)), y); - V8_TO_NUMBER(V8Helpers::Get(ctx, _this, V8Helpers::Vector3_ZKey(isolate)), z); - - V8_ARG_TO_OBJECT(1, vec); - - V8_TO_NUMBER(vec->Get(ctx, V8Helpers::Vector3_XKey(isolate)).ToLocalChecked(), x2); - V8_TO_NUMBER(vec->Get(ctx, V8Helpers::Vector3_YKey(isolate)).ToLocalChecked(), y2); - V8_TO_NUMBER(vec->Get(ctx, V8Helpers::Vector3_ZKey(isolate)).ToLocalChecked(), z2); - - double xy = x * x2 + y * y2; - double posALength = sqrt(std::pow(x, 2) + std::pow(y, 2)); - double posBLength = sqrt(std::pow(x2, 2) + std::pow(y2, 2)); - - if(posALength == 0 || posBLength == 0) - { - V8Helpers::Throw(isolate, "Division by zero!"); - return; - } - - double cos = xy / (posALength * posBLength); - double radians = std::acos(cos); - double angle = radians * (180 / PI); - - V8_RETURN_NUMBER(angle); -} -*/ - -static void Constructor(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_CONSTRUCTOR(); - V8_CHECK_ARGS_LEN2(1, 3); - - v8::Local _this = info.This(); - V8Helpers::SetObjectClass(isolate, _this, V8Class::ObjectClass::VECTOR3); - - v8::Local x, y, z; - - if(info.Length() == 3) - { - V8_ARG_CHECK_NUMBER(1); - V8_ARG_CHECK_NUMBER(2); - V8_ARG_CHECK_NUMBER(3); - - x = info[0]; - y = info[1]; - z = info[2]; - } - else - { - v8::Local val = info[0]; - - if(val->IsArray()) - { - v8::Local arr = val.As(); - V8_CHECK(arr->Length() == 3, "Argument must be an array of 3 numbers"); - - x = arr->Get(ctx, 0).ToLocalChecked(); - y = arr->Get(ctx, 1).ToLocalChecked(); - z = arr->Get(ctx, 2).ToLocalChecked(); - - V8_CHECK(x->IsNumber(), "Argument must be an array of 3 numbers"); - V8_CHECK(y->IsNumber(), "Argument must be an array of 3 numbers"); - V8_CHECK(z->IsNumber(), "Argument must be an array of 3 numbers"); - } - else if(val->IsObject()) - { - v8::Local obj = val.As(); - - x = obj->Get(ctx, resource->XKey()).ToLocalChecked(); - y = obj->Get(ctx, resource->YKey()).ToLocalChecked(); - z = obj->Get(ctx, resource->ZKey()).ToLocalChecked(); - - V8_CHECK(x->IsNumber(), "x must be a number"); - V8_CHECK(y->IsNumber(), "y must be a number"); - V8_CHECK(z->IsNumber(), "z must be a number"); - } - else if(val->IsNumber()) - { - x = val; - y = val; - z = val; - } - else - { - V8Helpers::Throw(isolate, "Argument must be an array of 3 numbers or IVector3"); - return; - } - } - - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->XKey(), x, v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->YKey(), y, v8::PropertyAttribute::ReadOnly); - V8Helpers::DefineOwnProperty(isolate, ctx, _this, resource->ZKey(), z, v8::PropertyAttribute::ReadOnly); -} - -extern V8Class v8Vector3("Vector3", - Constructor, - [](v8::Local tpl) - { - tpl->InstanceTemplate()->SetInternalFieldCount(static_cast(V8Class::InternalFields::COUNT)); - }); diff --git a/shared/bindings/Vector3.js b/shared/bindings/Vector3.js deleted file mode 100644 index 8f65d83b..00000000 --- a/shared/bindings/Vector3.js +++ /dev/null @@ -1,197 +0,0 @@ -/// -// clang-format off -// Vector3 JS bindings - -function getXYZFromArgs(args) { - alt.Utils.assert(args.length === 1 || args.length === 3, "1 or 3 arguments expected"); - - const firstArg = args[0]; - let x = 0, y = 0, z = 0; - - if(args.length === 3) { - x = parseFloat(firstArg); - y = parseFloat(args[1]); - z = parseFloat(args[2]); - - assertNotNaN(x, "Expected a number as first argument"); - assertNotNaN(y, "Expected a number as second argument"); - assertNotNaN(z, "Expected a number as third argument"); - } - else { - if(typeof firstArg === "number" || typeof firstArg === "string") { - const number = parseFloat(firstArg); - assertNotNaN(number, "Expected a number or string as first argument"); - - x = number; - y = number; - z = number; - } - else if(Array.isArray(firstArg)) { - if(typeof firstArg[0] === "number" || typeof firstArg[0] === "string") { - x = parseFloat(firstArg[0]); - assertNotNaN(x, "Expected an array of 3 numbers as first argument"); - } - if(typeof firstArg[1] === "number" || typeof firstArg[1] === "string") { - y = parseFloat(firstArg[1]); - assertNotNaN(y, "Expected an array of 3 numbers as first argument"); - } - if(typeof firstArg[2] === "number" || typeof firstArg[2] === "string") { - z = parseFloat(firstArg[2]); - assertNotNaN(z, "Expected an array of 3 numbers as first argument"); - } - } - else if(firstArg && typeof firstArg === "object") { - if(firstArg.x !== undefined) { - x = parseFloat(firstArg.x); - assertNotNaN(x, "Expected Vector3 as first argument"); - } - if(firstArg.y !== undefined) { - y = parseFloat(firstArg.y); - assertNotNaN(y, "Expected Vector3 as first argument"); - } - if(firstArg.z !== undefined) { - z = parseFloat(firstArg.z); - assertNotNaN(z, "Expected Vector3 as first argument"); - } - } - else throw new Error("Argument must be a number, an array of 3 numbers or IVector3"); - } - - return [x, y, z]; -} - -// Static properties -alt.Vector3.zero = new alt.Vector3(0, 0, 0); -alt.Vector3.one = new alt.Vector3(1, 1, 1); -alt.Vector3.back = new alt.Vector3(0, -1, 0); -alt.Vector3.up = new alt.Vector3(0, 0, 1); -alt.Vector3.down = new alt.Vector3(0, 0, -1); -alt.Vector3.forward = new alt.Vector3(0, 1, 0); -alt.Vector3.left = new alt.Vector3(-1, 0, 0); -alt.Vector3.right = new alt.Vector3(1, 0, 0); -alt.Vector3.negativeInfinity = new alt.Vector3(-Infinity, -Infinity, -Infinity); -alt.Vector3.positiveInfinity = new alt.Vector3(Infinity, Infinity, Infinity); - -// Static getters -Object.defineProperty(alt.Vector3.prototype, "length", { - get: function() { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } -}); - -// Instance methods -alt.Vector3.prototype.toString = function() { - return `Vector3{ x: ${this.x.toFixed(4)}, y: ${this.y.toFixed(4)}, z: ${this.z.toFixed(4)} }`; -} - -alt.Vector3.prototype.toArray = function() { - return [this.x, this.y, this.z]; -} - -alt.Vector3.prototype.toFixed = function(precision = 4) { - return new alt.Vector3( - parseFloat(this.x.toFixed(precision)), - parseFloat(this.y.toFixed(precision)), - parseFloat(this.z.toFixed(precision)) - ); -} - -alt.Vector3.prototype.add = function(...args) { - const [x, y, z] = getXYZFromArgs(args); - return new alt.Vector3(this.x + x, this.y + y, this.z + z); -} - -alt.Vector3.prototype.sub = function(...args) { - const [x, y, z] = getXYZFromArgs(args); - return new alt.Vector3(this.x - x, this.y - y, this.z - z); -} - -alt.Vector3.prototype.div = function(...args) { - const [x, y, z] = getXYZFromArgs(args); - return new alt.Vector3(this.x / x, this.y / y, this.z / z); -} - -alt.Vector3.prototype.mul = function(...args) { - const [x, y, z] = getXYZFromArgs(args); - return new alt.Vector3(this.x * x, this.y * y, this.z * z); -} - -alt.Vector3.prototype.dot = function(...args) { - const [x, y, z] = getXYZFromArgs(args); - return (this.x * x) + (this.y * y) + (this.z * z); -} - -alt.Vector3.prototype.cross = function(...args) { - const [x, y, z] = getXYZFromArgs(args); - return new alt.Vector3((this.y * z) - (this.z * y), (this.z * x) - (this.x * z), (this.x * y) - (this.y * x)); -} - -alt.Vector3.prototype.negative = function() { - return new alt.Vector3(-this.x, -this.y, -this.z); -} - -alt.Vector3.prototype.inverse = function() { - return new alt.Vector3(1 / this.x, 1 / this.y, 1 / this.z); -} - -alt.Vector3.prototype.normalize = function() { - const length = this.length; - return new alt.Vector3(this.x == 0 ? 0 : this.x / length, this.y == 0 ? 0: this.y / length, this.z == 0 ? 0 : this.z / length); -} - -alt.Vector3.prototype.distanceTo = function(vector) { - return Math.sqrt(this.distanceToSquared(vector)); -} - -alt.Vector3.prototype.distanceToSquared = function(vector) { - alt.Utils.assert(vector != null, "Expected Vector3 as first argument"); - - const x = this.x - parseFloat(vector.x); - const y = this.y - parseFloat(vector.y); - const z = this.z - parseFloat(vector.z); - return x * x + y * y + z * z; -} - -alt.Vector3.prototype.angleTo = function(vector) { - alt.Utils.assert(vector != null, "Expected Vector3 as first argument"); - - const posALength = Math.hypot(this.x, this.y, this.z); - const posBLength = Math.hypot(vector.x, vector.y, vector.z); - if (posALength === 0 || posBLength === 0) throw new Error("Division by zero"); - - return Math.acos((this.x * vector.x + this.y * vector.y + this.z * vector.z) / (posALength * posBLength)); -} - -alt.Vector3.prototype.angleToDegrees = function(vector) { - return this.angleTo(vector) * (180 / Math.PI); -} - -alt.Vector3.prototype.toDegrees = function() { - return new alt.Vector3((this.x * 180) / Math.PI, (this.y * 180) / Math.PI, (this.z * 180) / Math.PI); -} - -alt.Vector3.prototype.toRadians = function() { - return new alt.Vector3((this.x * Math.PI) / 180, (this.y * Math.PI) / 180, (this.z * Math.PI) / 180); -} - -alt.Vector3.prototype.isInRange = function(vector, range) { - alt.Utils.assert(vector != null, "Expected Vector3 as first argument"); - alt.Utils.assert(typeof range === "number", "Expected a number as second argument"); - - const x = Math.abs(this.x - parseFloat(vector.x)); - const y = Math.abs(this.y - parseFloat(vector.y)); - const z = Math.abs(this.z - parseFloat(vector.z)); - - return x <= range && y <= range && z <= range // Fast check - && x * x + y * y + z * z <= range * range; // Slow check -} - -alt.Vector3.prototype.lerp = function(vector, ratio) { - alt.Utils.assert(vector != null, "Expected Vector3 as first argument"); - alt.Utils.assert(typeof ratio === "number", "Expected a number as second argument"); - - const x = this.x + (parseFloat(vector.x) - this.x) * ratio; - const y = this.y + (parseFloat(vector.y) - this.y) * ratio; - const z = this.z + (parseFloat(vector.z) - this.z) * ratio; - return new alt.Vector3(x, y, z); -} diff --git a/shared/bindings/js/classes/quaternion.js b/shared/bindings/js/classes/quaternion.js new file mode 100644 index 00000000..8773ade2 --- /dev/null +++ b/shared/bindings/js/classes/quaternion.js @@ -0,0 +1,183 @@ +// clang-format off + +export class Quaternion { + #x = 0; + #y = 0; + #z = 0; + #w = 0; + + constructor(...args) { + this.#x = x; + this.#y = y; + this.#z = z; + this.#w = w; + if (args.length === 4) { + this.#x = parseInt(args[0]); + this.#y = parseInt(args[1]); + this.#z = parseInt(args[2]); + this.#w = parseInt(args[3]); + } else if (args.length === 1) { + const arg = args[0]; + if (Array.isArray(arg)) { + this.#x = parseInt(arg[0]); + this.#y = parseInt(arg[1]); + this.#z = parseInt(arg[2]); + this.#w = parseInt(arg[3]); + } else if (typeof arg === "object") { + this.#x = parseInt(arg.x); + this.#y = parseInt(arg.y); + this.#z = parseInt(arg.z); + this.#w = parseInt(arg.w); + } + } + } + + get x() { + return this.#x; + } + get y() { + return this.#y; + } + get z() { + return this.#z; + } + get w() { + return this.#w; + } + + get length() { + return Math.sqrt(this.lengthSquared); + } + + get lengthSquared() { + return this.#x * this.#x + this.#y * this.#y + this.#z * this.#z + this.#w * this.#w; + } + + get conjugate() { + return new Quaternion(-this.#x, -this.#y, -this.#z, this.#w); + } + + get inverse() { + return this.conjugate.multiply(1 / this.lengthSquared); + } + + get normalized() { + return this.multiply(1 / this.length); + } + + get pitch() { + return Math.atan2(2 * (this.#w * this.#x + this.#y * this.#z), 1 - 2 * (this.#x * this.#x + this.#y * this.#y)); + } + + get yaw() { + return Math.asin(2 * (this.#w * this.#y - this.#z * this.#x)); + } + + get roll() { + return Math.atan2(2 * (this.#w * this.#z + this.#x * this.#y), 1 - 2 * (this.#y * this.#y + this.#z * this.#z)); + } + + get eulerAngles() { + return new Vector3(this.pitch, this.yaw, this.roll); + } + + get axis() { + const s = Math.sqrt(1 - this.#w * this.#w); + if (s < 0.001) { + return new Vector3(this.#x, this.#y, this.#z); + } else { + return new Vector3(this.#x / s, this.#y / s, this.#z / s); + } + } + + get angle() { + return 2 * Math.acos(this.#w); + } + + get matrix() { + const x = this.#x; + const y = this.#y; + const z = this.#z; + const w = this.#w; + + const xx = x * x; + const xy = x * y; + const xz = x * z; + const xw = x * w; + + const yy = y * y; + const yz = y * z; + const yw = y * w; + + const zz = z * z; + const zw = z * w; + + return [1 - 2 * (yy + zz), 2 * (xy - zw), 2 * (xz + yw), 0, 2 * (xy + zw), 1 - 2 * (xx + zz), 2 * (yz - xw), 0, 2 * (xz - yw), 2 * (yz + xw), 1 - 2 * (xx + yy), 0, 0, 0, 0, 1]; + } + + get forward() { + return new Vector3(2 * (this.#x * this.#z + this.#w * this.#y), 2 * (this.#y * this.#z - this.#w * this.#x), 1 - 2 * (this.#x * this.#x + this.#y * this.#y)); + } + + get right() { + return new Vector3(1 - 2 * (this.#y * this.#y + this.#z * this.#z), 2 * (this.#x * this.#y + this.#w * this.#z), 2 * (this.#x * this.#z - this.#w * this.#y)); + } + + get up() { + return new Vector3(2 * (this.#x * this.#y - this.#w * this.#z), 1 - 2 * (this.#x * this.#x + this.#z * this.#z), 2 * (this.#y * this.#z + this.#w * this.#x)); + } + + get left() { + return new Vector3(1 - 2 * (this.#y * this.#y + this.#z * this.#z), 2 * (this.#x * this.#y - this.#w * this.#z), 2 * (this.#x * this.#z + this.#w * this.#y)); + } + + get back() { + return new Vector3(2 * (this.#x * this.#z - this.#w * this.#y), 2 * (this.#y * this.#z + this.#w * this.#x), 1 - 2 * (this.#x * this.#x + this.#y * this.#y)); + } + + get down() { + return new Vector3(2 * (this.#x * this.#y + this.#w * this.#z), 1 - 2 * (this.#x * this.#x + this.#z * this.#z), 2 * (this.#y * this.#z - this.#w * this.#x)); + } + + static fromEuler(x, y, z) { + const c1 = Math.cos(x / 2); + const c2 = Math.cos(y / 2); + const c3 = Math.cos(z / 2); + const s1 = Math.sin(x / 2); + const s2 = Math.sin(y / 2); + const s3 = Math.sin(z / 2); + + return new Quaternion(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3); + } + + static fromAxisAngle(axis, angle) { + const halfAngle = angle / 2; + const s = Math.sin(halfAngle); + + return new Quaternion(axis.x * s, axis.y * s, axis.z * s, Math.cos(halfAngle)); + } + + static fromMatrix(matrix) { + const trace = matrix[0] + matrix[5] + matrix[10]; + let S = 0; + + if (trace > 0) { + S = Math.sqrt(trace + 1.0) * 2; + return new Quaternion((matrix[6] - matrix[9]) / S, (matrix[8] - matrix[2]) / S, (matrix[1] - matrix[4]) / S, 0.25 * S); + } else if (matrix[0] > matrix[5] && matrix[0] > matrix[10]) { + S = Math.sqrt(1.0 + matrix[0] - matrix[5] - matrix[10]) * 2; + return new Quaternion(0.25 * S, (matrix[1] + matrix[4]) / S, (matrix[8] + matrix[2]) / S, (matrix[6] - matrix[9]) / S); + } else if (matrix[5] > matrix[10]) { + S = Math.sqrt(1.0 + matrix[5] - matrix[0] - matrix[10]) * 2; + return new Quaternion((matrix[1] + matrix[4]) / S, 0.25 * S, (matrix[6] + matrix[9]) / S, (matrix[8] - matrix[2]) / S); + } else { + S = Math.sqrt(1.0 + matrix[10] - matrix[0] - matrix[5]) * 2; + return new Quaternion((matrix[8] + matrix[2]) / S, (matrix[6] + matrix[9]) / S, 0.25 * S, (matrix[1] - matrix[4]) / S); + } + } + + static fromArray(array) { + return new Quaternion(array[0], array[1], array[2], array[3]); + } +} +cppBindings.registerExport(cppBindings.BindingExport.QUATERNION_CLASS, Quaternion); diff --git a/shared/bindings/js/classes/rgba.js b/shared/bindings/js/classes/rgba.js new file mode 100644 index 00000000..4dcd360f --- /dev/null +++ b/shared/bindings/js/classes/rgba.js @@ -0,0 +1,60 @@ +// clang-format off + +export class RGBA { + r = 0; + g = 0; + b = 0; + a = 255; + + constructor(...args) { + if (args.length >= 3) { + this.r = args[0]; + this.g = args[1]; + this.b = args[2]; + if (args.length === 4) this.a = args[3]; + } else if (args.length === 1) { + const arg = args[0]; + if (typeof arg === "object") { + const obj = arg; + this.r = obj.r; + this.g = obj.g; + this.b = obj.b; + if (obj.a) this.a = obj.a; + } else if (Array.isArray(arg)) { + const arr = arg; + this.r = arr[0]; + this.g = arr[1]; + this.b = arr[2]; + if (arr.length === 4) this.a = arr[3]; + } + } else throw new Error("Invalid arguments"); + } + + toArray() { + return [this.r, this.g, this.b, this.a]; + } + + toInt() { + let int = this.r << 24; + int |= this.g << 16; + int |= this.b << 8; + int |= this.a; + return int; + } + + static fromInt(int) { + const r = (int >> 24) & 0xff; + const g = (int >> 16) & 0xff; + const b = (int >> 8) & 0xff; + const a = int & 0xff; + return new RGBA(r, g, b, a); + } + + static red = new RGBA(255, 0, 0, 255); + static green = new RGBA(0, 255, 0, 255); + static blue = new RGBA(0, 0, 255, 255); + static black = new RGBA(0, 0, 0, 255); + static white = new RGBA(255, 255, 255, 255); + static clear = new RGBA(0, 0, 0, 0); +} +cppBindings.registerExport(cppBindings.BindingExport.RGBA_CLASS, RGBA); diff --git a/shared/bindings/js/classes/vector2.js b/shared/bindings/js/classes/vector2.js new file mode 100644 index 00000000..6114be11 --- /dev/null +++ b/shared/bindings/js/classes/vector2.js @@ -0,0 +1,144 @@ +// clang-format off + +const { valueToNumber } = requireBinding("shared/helpers/classes.js"); + +export class Vector2 { + constructor(...args) { + [this.x, this.y] = this.#getArgValues(...args); + Object.freeze(this); + } + + add(...args) { + const [x, y] = this.#getArgValues(...args); + return new Vector2(this.x + x, this.y + y); + } + + sub(...args) { + const [x, y] = this.#getArgValues(...args); + return new Vector2(this.x - x, this.y - y); + } + + mul(...args) { + const [x, y] = this.#getArgValues(...args); + return new Vector2(this.x * x, this.y * y); + } + + div(...args) { + const [x, y] = this.#getArgValues(...args); + if (x === 0 || y === 0) { + throw new Error("Division by zero"); + } + return new Vector2(this.x / x, this.y / y); + } + + dot(...args) { + const [x, y] = this.#getArgValues(...args); + return this.x * x + this.y * y; + } + + get lengthSquared() { + return this.x * this.x + this.y * this.y; + } + + get length() { + return Math.sqrt(this.lengthSquared); + } + + get normalized() { + const length = this.length; + if (length === 0) { + return Vector2.zero; + } + return new Vector2(this.x / length, this.y / length); + } + + get inverse() { + return new Vector2(this.x ? 1 / this.x : 0, this.y ? 1 / this.y : 0); + } + + get negative() { + return new Vector2(-this.x, -this.y); + } + + angleTo(...args) { + const [x, y] = this.#getArgValues(...args); + const mag1 = this.length; + const mag2 = Math.sqrt(x * x + y * y); + const dot = this.dot(x, y); + return Math.acos(dot / (mag1 * mag2)); + } + + angleToDegrees(...args) { + return (this.angleTo(...args) * 180) / Math.PI; + } + + distanceTo(...args) { + const [x, y] = this.#getArgValues(...args); + const dx = this.x - x; + const dy = this.y - y; + return Math.sqrt(dx * dx + dy * dy); + } + + distanceToSquared(...args) { + const [x, y] = this.#getArgValues(...args); + const dx = this.x - x; + const dy = this.y - y; + return dx * dx + dy * dy; + } + + toArray() { + return [this.x, this.y]; + } + + toFixed(precision = 4) { + return new Vector2(parseFloat(this.x.toFixed(precision)), parseFloat(this.y.toFixed(precision))); + } + + toRadians() { + return new Vector2((this.x * Math.PI) / 180, (this.y * Math.PI) / 180); + } + + toDegrees() { + return new Vector2((this.x * 180) / Math.PI, (this.y * 180) / Math.PI); + } + + isInRange(range, ...args) { + const [x, y] = this.#getArgValues(...args); + return Math.abs(this.x - x) <= range && Math.abs(this.y - y) <= range; + } + + lerp(alpha, ...args) { + const [x, y] = this.#getArgValues(...args); + return new Vector2(this.x + alpha * (x - this.x), this.y + alpha * (y - this.y)); + } + + #getArgValues(...args) { + let values = []; + if (args.length === 2) values = args; + else if (args.length === 1) { + const arg = args[0]; + if (arg instanceof Vector2) values = [arg.x, arg.y]; + else if (Array.isArray(arg)) values = arg; + else if (typeof arg === "object") values = Object.values(arg); + else values = [arg, arg]; + } else throw new Error("Invalid arguments"); + return values.map(valueToNumber); + } + + static fromArray(arr) { + return new Vector2(arr[0], arr[1]); + } + static fromObject(obj) { + return new Vector2(obj.x, obj.y); + } + + static zero = new Vector2(0, 0); + static one = new Vector2(1, 1); + static up = new Vector2(0, 1); + static down = new Vector2(0, -1); + static left = new Vector2(-1, 0); + static right = new Vector2(1, 0); + static negativeInfinity = new Vector2(-Infinity, -Infinity); + static positiveInfinity = new Vector2(Infinity, Infinity); +} +cppBindings.registerExport(cppBindings.BindingExport.VECTOR2_CLASS, Vector2); \ No newline at end of file diff --git a/shared/bindings/js/classes/vector3.js b/shared/bindings/js/classes/vector3.js new file mode 100644 index 00000000..476e296a --- /dev/null +++ b/shared/bindings/js/classes/vector3.js @@ -0,0 +1,153 @@ +// clang-format off + +const { valueToNumber } = requireBinding("shared/helpers/classes.js"); + +export class Vector3 { + constructor(...args) { + [this.x, this.y, this.z] = this.#getArgValues(...args); + Object.freeze(this); + } + + add(...args) { + const [x, y, z] = this.#getArgValues(...args); + return new Vector3(this.x + x, this.y + y, this.z + z); + } + + sub(...args) { + const [x, y, z] = this.#getArgValues(...args); + return new Vector3(this.x - x, this.y - y, this.z - z); + } + + mul(...args) { + const [x, y, z] = this.#getArgValues(...args); + return new Vector3(this.x * x, this.y * y, this.z * z); + } + + div(...args) { + const [x, y, z] = this.#getArgValues(...args); + if (x === 0 || y === 0 || z === 0) { + throw new Error("Division by zero"); + } + return new Vector3(this.x / x, this.y / y, this.z / z); + } + + dot(...args) { + const [x, y, z] = this.#getArgValues(...args); + return this.x * x + this.y * y + this.z * z; + } + + cross(...args) { + const [x, y, z] = this.#getArgValues(...args); + return new Vector3(this.y * z - this.z * y, this.z * x - this.x * z, this.x * y - this.y * x); + } + + get lengthSquared() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + + get length() { + return Math.sqrt(this.lengthSquared); + } + + get normalized() { + const length = this.length; + if (length === 0) { + return Vector3.zero; + } + return new Vector3(this.x / length, this.y / length, this.z / length); + } + + get inverse() { + return new Vector3(this.x ? 1 / this.x : 0, this.y ? 1 / this.y : 0, this.z ? 1 / this.z : 0); + } + + get negative() { + return new Vector3(-this.x, -this.y, -this.z); + } + + angleTo(...args) { + const [x, y, z] = this.#getArgValues(...args); + const mag1 = this.length; + const mag2 = Math.sqrt(x * x + y * y + z * z); + const dot = this.dot(x, y, z); + return Math.acos(dot / (mag1 * mag2)); + } + + angleToDegrees(...args) { + return this.angleTo(...args) * (180 / Math.PI); + } + + distanceTo(...args) { + const [x, y, z] = this.#getArgValues(...args); + const dx = this.x - x; + const dy = this.y - y; + const dz = this.z - z; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } + + distanceToSquared(...args) { + const [x, y, z] = this.#getArgValues(...args); + const dx = this.x - x; + const dy = this.y - y; + const dz = this.z - z; + return dx * dx + dy * dy + dz * dz; + } + + toArray() { + return [this.x, this.y, this.z]; + } + + toFixed(precision = 4) { + return new Vector3(parseFloat(this.x.toFixed(precision)), parseFloat(this.y.toFixed(precision)), parseFloat(this.z.toFixed(precision))); + } + + toDegrees() { + return new Vector3(this.x * (180 / Math.PI), this.y * (180 / Math.PI), this.z * (180 / Math.PI)); + } + + toRadians() { + return new Vector3(this.x * (Math.PI / 180), this.y * (Math.PI / 180), this.z * (Math.PI / 180)); + } + + isInRange(range, ...args) { + const [x, y, z] = this.#getArgValues(...args); + return Math.abs(this.x - x) <= range && Math.abs(this.y - y) <= range && Math.abs(this.z - z) <= range; + } + + lerp(alpha, ...args) { + const [x, y, z] = this.#getArgValues(...args); + return new Vector3(this.x + (x - this.x) * alpha, this.y + (y - this.y) * alpha, this.z + (z - this.z) * alpha); + } + + #getArgValues(...args) { + let values = []; + if (args.length === 3) values = args; + else if (args.length === 1) { + const arg = args[0]; + if (arg instanceof Vector3) values = [arg.x, arg.y, arg.z]; + else if (Array.isArray(arg)) values = arg; + else if (typeof arg === "object") values = Object.values(arg); + else values = [arg, arg, arg]; + } else throw new Error("Invalid arguments"); + return values.map(valueToNumber); + } + + static fromArray(arr) { + return new Vector3(arr[0], arr[1], arr[2]); + } + static fromObject(obj) { + return new Vector3(obj.x, obj.y, obj.z); + } + + static zero = new Vector3(0, 0, 0); + static one = new Vector3(1, 1, 1); + static up = new Vector3(0, 0, 1); + static down = new Vector3(0, 0, -1); + static forward = new Vector3(0, 1, 0); + static back = new Vector3(0, -1, 0); + static left = new Vector3(-1, 0, 0); + static right = new Vector3(1, 0, 0); + static negativeInfinity = new Vector3(-Infinity, -Infinity, -Infinity); + static positiveInfinity = new Vector3(Infinity, Infinity, Infinity); +} +cppBindings.registerExport(cppBindings.BindingExport.VECTOR3_CLASS, Vector3); \ No newline at end of file diff --git a/shared/bindings/js/helpers/classes.js b/shared/bindings/js/helpers/classes.js new file mode 100644 index 00000000..f41a506a --- /dev/null +++ b/shared/bindings/js/helpers/classes.js @@ -0,0 +1,4 @@ +export function valueToNumber(val) { + if (val === Infinity || val === -Infinity) return val; + return parseFloat(val); +} \ No newline at end of file diff --git a/shared/bindings/Logging.js b/shared/bindings/js/logging.js similarity index 99% rename from shared/bindings/Logging.js rename to shared/bindings/js/logging.js index 46d2612c..3d18c332 100644 --- a/shared/bindings/Logging.js +++ b/shared/bindings/js/logging.js @@ -3193,6 +3193,6 @@ function genericLog(type, ...args) { }); __printLog(type, ...logArgs); } -__global.genericLog = genericLog; +globalThis.genericLog = genericLog; alt.Utils.inspect = inspect; diff --git a/shared/bindings/Utils.js b/shared/bindings/js/utils.js similarity index 99% rename from shared/bindings/Utils.js rename to shared/bindings/js/utils.js index 4ecb0142..c54a9900 100644 --- a/shared/bindings/Utils.js +++ b/shared/bindings/js/utils.js @@ -4,6 +4,8 @@ // clang-format off // Utils JS bindings +const native = cppBindings.getBuiltinModule("natives"); + // Shared class BaseUtility { diff --git a/shared/helpers/BindingHandler.h b/shared/helpers/BindingHandler.h new file mode 100644 index 00000000..a56f118f --- /dev/null +++ b/shared/helpers/BindingHandler.h @@ -0,0 +1,58 @@ +#pragma once + +#include "v8.h" +#include "JS.h" + +#include + +namespace js +{ + enum class BindingExport : uint8_t + { + // Classes + VECTOR3_CLASS, + VECTOR2_CLASS, + RGBA_CLASS, + QUATERNION_CLASS, + + SIZE, + }; + + class BindingHandler + { + public: + void SetBindingExport(BindingExport export_, v8::Local val) + { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + bindingExportsMap.insert_or_assign(export_, Persistent(isolate, val)); + } + + bool HasBindingExport(BindingExport export_) + { + auto it = bindingExportsMap.find(export_); + return it != bindingExportsMap.end() && !it->second.IsEmpty(); + } + + template + v8::Local GetBindingExport(BindingExport export_) + { + static_assert(std::is_base_of_v, "T must inherit from v8::Value"); + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + + auto it = bindingExportsMap.find(export_); + if (it != bindingExportsMap.end()) { + return it->second.Get(isolate).As(); + } + + return v8::Local(); + } + + void Reset() + { + bindingExportsMap.clear(); + } + + private: + std::unordered_map> bindingExportsMap; + }; +} // namespace js \ No newline at end of file diff --git a/shared/helpers/Bindings.cpp b/shared/helpers/Bindings.cpp index 3c4a14fb..1c96a82f 100644 --- a/shared/helpers/Bindings.cpp +++ b/shared/helpers/Bindings.cpp @@ -2,6 +2,8 @@ #include "Serialization.h" #include "V8ResourceImpl.h" +#include "magic_enum/magic_enum.hpp" + void V8Helpers::RegisterFunc(v8::Local exports, const std::string& _name, v8::FunctionCallback cb, void* data) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); @@ -21,6 +23,24 @@ void V8Helpers::RegisterFunc(v8::Local exports, const std::string& _ exports->Set(ctx, name, fn); } +void V8Helpers::RegisterBindingExport(v8::Local exports, const std::string& _name, js::BindingExport _export) +{ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local ctx = isolate->GetEnteredOrMicrotaskContext(); + V8ResourceImpl* resource = V8ResourceImpl::Get(ctx); + + v8::Local data = resource->GetBindingHandler()->GetBindingExport(_export); + if (data.IsEmpty()) + { + Log::Error << "Failed to register JSBinding for " + << magic_enum::enum_name(_export) + << ", data is empty!" + << Log::Endl; + return; + } + exports->Set(ctx, V8Helpers::JSValue(_name), data); +} + void V8Helpers::FunctionCallback(const v8::FunctionCallbackInfo& info) { auto fn = static_cast(info.Data().As()->Value()); @@ -35,7 +55,7 @@ void V8Helpers::FunctionCallback(const v8::FunctionCallbackInfo& info alt::MValue res = (*fn)->Call(args); - info.GetReturnValue().Set(V8Helpers::MValueToV8(res)); + V8_RETURN(V8Helpers::MValueToV8(res)); } void V8Helpers::SetAccessor(v8::Local tpl, v8::Isolate* isolate, const char* name, v8::AccessorGetterCallback getter, v8::AccessorSetterCallback setter) diff --git a/shared/helpers/Bindings.h b/shared/helpers/Bindings.h index 4be1e5b0..fc90243e 100644 --- a/shared/helpers/Bindings.h +++ b/shared/helpers/Bindings.h @@ -3,10 +3,29 @@ #include "v8.h" #include "cpp-sdk/ICore.h" #include "V8FastFunction.h" +#include "BindingHandler.h" + +#include "magic_enum/magic_enum.hpp" namespace V8Helpers { void RegisterFunc(v8::Local exports, const std::string& _name, v8::FunctionCallback cb, void* data = nullptr); + template + void RegisterEnum(v8::Local exports, const std::string& name) + { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local ctx = isolate->GetCurrentContext(); + + v8::Local enumObject = v8::Object::New(isolate); + + auto values = magic_enum::enum_entries(); + for(auto& [value, key] : values) + { + enumObject->Set(ctx, V8Helpers::JSValue(key.data()), JSValue(static_cast(std::to_string(static_cast(value))))).Check(); + } + exports->Set(ctx, v8::String::NewFromUtf8(isolate, name.c_str()).ToLocalChecked(), enumObject).Check(); + } + void RegisterBindingExport(v8::Local exports, const std::string& _name, js::BindingExport _export); void FunctionCallback(const v8::FunctionCallbackInfo& info); diff --git a/shared/helpers/Convert.h b/shared/helpers/Convert.h index 8a8295cf..002467be 100644 --- a/shared/helpers/Convert.h +++ b/shared/helpers/Convert.h @@ -44,7 +44,11 @@ namespace V8Helpers // * Function utilizing overloads to quickly convert a C++ value to a JS value // * These functions are defined in the header directly to utilize the 'inline' modifier - + inline v8::Local JSValue(std::string_view val) + { + if(val.size() == 0) return v8::String::Empty(v8::Isolate::GetCurrent()); + return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), val.data(), v8::NewStringType::kNormal, (int)val.size()).ToLocalChecked(); + } inline v8::Local JSValue(const char* val) { return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), val).ToLocalChecked(); diff --git a/shared/helpers/Filesystem.h b/shared/helpers/Filesystem.h new file mode 100644 index 00000000..70cc9a5d --- /dev/null +++ b/shared/helpers/Filesystem.h @@ -0,0 +1,24 @@ +#pragma once + +#include "cpp-sdk/ICore.h" + +namespace js +{ + static bool DoesFileExist(alt::IPackage* package, const std::string& path) + { + if(!package) return false; + return package->FileExists(path); + } + + static std::vector ReadFile(alt::IPackage* package, const std::string& path) + { + if(!DoesFileExist(package, path)) return {}; + alt::IPackage::File* file = package->OpenFile(path); + if(file == nullptr) return {}; + size_t fileSize = package->GetFileSize(file); + std::vector content(fileSize); + package->ReadFile(file, content.data(), fileSize); + package->CloseFile(file); + return content; + } +} // namespace js diff --git a/shared/helpers/JS.cpp b/shared/helpers/JS.cpp new file mode 100644 index 00000000..eb38bd86 --- /dev/null +++ b/shared/helpers/JS.cpp @@ -0,0 +1,40 @@ +#include "JS.h" + +#include "V8ResourceImpl.h" + +static void FunctionHandler(const v8::FunctionCallbackInfo& info) +{ + auto callback = reinterpret_cast(info.Data().As()->Value()); + callback(info); +} + +static v8::Local WrapFunction(v8::FunctionCallback cb) +{ + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Local tpl = v8::FunctionTemplate::New(isolate, FunctionHandler, v8::External::New(isolate, (void*)cb)); + return tpl; +} + +js::TemporaryGlobalExtension::TemporaryGlobalExtension(v8::Local _ctx, const std::string& _name, v8::FunctionCallback _callback) + : ctx(_ctx), name(_name) +{ + ctx->Global()->Set(ctx, V8Helpers::JSValue(_name), WrapFunction(_callback)->GetFunction(ctx).ToLocalChecked()).Check(); +} + +bool js::Promise::Await() +{ + v8::Local promise = Get(); + while(true) + { + v8::Promise::PromiseState state = promise->State(); + + switch (state) + { + case v8::Promise::PromiseState::kPending: resource->OnTick(); break; + case v8::Promise::PromiseState::kFulfilled: return true; + case v8::Promise::PromiseState::kRejected: return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} diff --git a/shared/helpers/JS.h b/shared/helpers/JS.h new file mode 100644 index 00000000..258d1c66 --- /dev/null +++ b/shared/helpers/JS.h @@ -0,0 +1,195 @@ +#pragma once + +#include "v8.h" +#include "helpers/Convert.h" + +class V8ResourceImpl; + +namespace js +{ + template + using Persistent = v8::Persistent>; + + class ExternalString : public v8::String::ExternalOneByteStringResource + { + const char* str; + size_t size; + + public: + explicit ExternalString(const char* _str, size_t _size) : str(_str), size(_size) {} + ExternalString() : str(nullptr), size(0) {} + + ExternalString(const ExternalString&) = delete; + ExternalString& operator=(const ExternalString&) = delete; + + const char* data() const override + { + return str; + } + size_t length() const override + { + return size; + } + + void Dispose() override + { + // We should only do this for "eternal" strings that never get deleted, + // so don't do anything here + } + }; + + struct TemporaryGlobalExtension + { + std::string name; + v8::Local ctx; + + TemporaryGlobalExtension(v8::Local _ctx, const std::string& _name, v8::Local value) + : name(_name), ctx(_ctx) + { + ctx->Global()->Set(ctx, V8Helpers::JSValue(_name), value).Check(); + } + TemporaryGlobalExtension(v8::Local _ctx, const std::string& _name, v8::FunctionCallback _callback); + ~TemporaryGlobalExtension() + { + ctx->Global()->Delete(ctx, V8Helpers::JSValue(name)).Check(); + } + }; + + inline v8::Local CachedString(std::string_view val) + { + return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), val.data(), v8::NewStringType::kInternalized, (int)val.size()).ToLocalChecked(); + } + + // Falls back to default value if the value is not found or the type doesn't match + inline std::string GetV8ObjectValue(v8::Local context, v8::Local object, const std::string& key, const std::string& defaultValue = "") + { + v8::Isolate* isolate = context->GetIsolate(); + v8::Local v8Key = v8::String::NewFromUtf8(isolate, key.c_str(), v8::NewStringType::kNormal).ToLocalChecked(); + v8::Local value = object->Get(isolate->GetCurrentContext(), v8Key).ToLocalChecked(); + // Check if the value is a string + if (!value->IsString()) { + return defaultValue; + } + + // Convert the V8 string to a C++ string + v8::String::Utf8Value utf8Value(isolate, value); + return std::string(*utf8Value, utf8Value.length()); + } + + class TryCatch + { + v8::TryCatch tryCatch; + + void PrintError(bool skipLocation); + + public: + TryCatch() : tryCatch(v8::Isolate::GetCurrent()) {} + TryCatch(v8::Isolate* isolate) : tryCatch(isolate) {} + + bool Check(bool printError = true, bool skipLocation = false) + { + if(HasCaught()) + { + if(printError) PrintError(skipLocation); + tryCatch.Reset(); + return true; + } + return false; + } + + void ReThrow() + { + tryCatch.ReThrow(); + } + + bool HasCaught() const + { + return tryCatch.HasCaught(); + } + }; + + class PersistentValue + { + bool valid = true; + + protected: + v8::Isolate* isolate; + Persistent context; + + V8ResourceImpl* resource = nullptr; + + PersistentValue(bool _valid, v8::Local _context = v8::Isolate::GetCurrent()->GetEnteredOrMicrotaskContext()) + : valid(_valid), isolate(_context->GetIsolate()), context(_context->GetIsolate(), _context) + { + } + + public: + bool IsValid() const + { + return valid; + } + + v8::Isolate* GetIsolate() const + { + return isolate; + } + + v8::Local GetContext() const + { + return context.Get(isolate); + } + + V8ResourceImpl* GetResource(); + }; + + class Promise : public PersistentValue + { + friend class V8ResourceImpl; + + public: + using V8Type = v8::Promise::Resolver; + + private: + Persistent resolver; + mutable Persistent promise; + V8ResourceImpl* resource; + bool owned; + + Promise(V8ResourceImpl* _resource) + : PersistentValue(true), resolver(v8::Isolate::GetCurrent(), v8::Promise::Resolver::New(GetContext()).ToLocalChecked()), resource(_resource), owned(true) + { + } + + public: + Promise() : PersistentValue(true), resolver(v8::Isolate::GetCurrent(), v8::Promise::Resolver::New(GetContext()).ToLocalChecked()), owned(false) {} + Promise(v8::Local _promise) : PersistentValue(!_promise.IsEmpty()), promise(v8::Isolate::GetCurrent(), _promise), owned(false) {} + ~Promise(); + + v8::Local Get() const + { + if(!HasPromise() && HasResolver()) promise.Reset(v8::Isolate::GetCurrent(), GetResolver()->GetPromise()); + return promise.Get(v8::Isolate::GetCurrent()); + } + + v8::Local GetResolver() const + { + return resolver.Get(v8::Isolate::GetCurrent()); + } + + bool HasResolver() const + { + return !resolver.IsEmpty(); + } + bool HasPromise() const + { + return !promise.IsEmpty(); + } + + v8::Promise::PromiseState State() + { + return Get()->State(); + } + + bool Await(); + }; +} \ No newline at end of file diff --git a/shared/helpers/JSBindings.cpp b/shared/helpers/JSBindings.cpp new file mode 100644 index 00000000..598bbc96 --- /dev/null +++ b/shared/helpers/JSBindings.cpp @@ -0,0 +1,57 @@ +#include "helpers/JSBindings.h" +#include "V8ResourceImpl.h" + +static v8::MaybeLocal ResolveModuleCallback(v8::Local context, v8::Local specifier, v8::Local assertions, v8::Local referrer) +{ + return v8::MaybeLocal(); +} + +v8::Local js::Binding::Compile(V8ResourceImpl* resource) +{ + v8::Isolate* isolate = resource->GetIsolate(); + v8::Local context = resource->GetContext(); + std::string moduleName = "internal:" + name; + v8::ScriptOrigin origin{ isolate, V8Helpers::JSValue(moduleName), -2, 0, false, -1, v8::Local(), false, false, true, v8::Local() }; + v8::ScriptCompiler::Source source{ v8::String::NewExternalOneByte(isolate, src).ToLocalChecked(), origin }; + v8::MaybeLocal maybeModule = v8::ScriptCompiler::CompileModule(isolate, &source); + if(maybeModule.IsEmpty()) + { + Log::Error << "INTERNAL ERROR: Failed to compile bindings module " << name << Log::Endl; + return v8::Local(); + } + v8::Local mod = maybeModule.ToLocalChecked(); + v8::Maybe result = mod->InstantiateModule(context, &ResolveModuleCallback); + if(result.IsNothing() || !result.ToChecked() || mod->GetStatus() != v8::Module::kInstantiated) + { + Log::Error << "INTERNAL ERROR: Failed to instantiate bindings module " << name << Log::Endl; + return v8::Local(); + } + compiledModuleMap.insert({ resource, std::move(Persistent(isolate, mod)) }); + return mod; +} + +v8::Local js::Binding::GetCompiledModule(V8ResourceImpl* resource) +{ + v8::Isolate* isolate = resource->GetIsolate(); + v8::Local mod; + + auto it = compiledModuleMap.find(resource); + if(it == compiledModuleMap.end()) mod = Compile(resource); + else + mod = it->second.Get(isolate); + + return mod; +} + +std::vector js::Binding::GetBindingsForScope(Scope scope) +{ + std::vector bindings; + for(auto& [_, binding] : __bindings) + if(binding.scope == Scope::SHARED || binding.scope == scope) bindings.push_back(&binding); + return bindings; +} + +void js::Binding::CleanupForResource(V8ResourceImpl* resource) +{ + for(auto& [_, binding] : __bindings) binding.compiledModuleMap.erase(resource); +} \ No newline at end of file diff --git a/shared/helpers/JSBindings.h b/shared/helpers/JSBindings.h new file mode 100644 index 00000000..d79c07a7 --- /dev/null +++ b/shared/helpers/JSBindings.h @@ -0,0 +1,74 @@ +#pragma once + +#include "v8.h" +#include "BindingHandler.h" +#include "magic_enum/magic_enum.hpp" + +class V8ResourceImpl; + +namespace js +{ + class Binding + { + static std::unordered_map __bindings; + + public: + enum class Scope : uint8_t + { + SHARED, + SERVER, + CLIENT + }; + + private: + bool valid = false; + std::string name; + Scope scope; + ExternalString* src = nullptr; + std::unordered_map> compiledModuleMap; + + v8::Local Compile(V8ResourceImpl* resource); + + public: + Binding() = default; + Binding(const std::string& _name, Scope _scope, const std::vector& _src) : valid(true), name(_name), scope(_scope) + { + char* data = new char[_src.size() + 1]; + memcpy(data, _src.data(), _src.size()); + data[_src.size()] = '\0'; + src = new ExternalString(data, _src.size()); + } + + bool IsValid() const + { + return valid; + } + const std::string& GetName() const + { + return name; + } + Scope GetScope() const + { + return scope; + } + const char* GetSource() const + { + return src->data(); + } + v8::Local GetCompiledModule(V8ResourceImpl* resource); + + bool IsBootstrapBinding() const + { + return name.ends_with("bootstrap.js"); + } + + static std::vector GetBindingsForScope(Scope scope); + static Binding& Get(const std::string& name) + { + static Binding invalidBinding{}; + if(!__bindings.contains(name)) return invalidBinding; + return __bindings.at(name); + } + static void CleanupForResource(V8ResourceImpl* resource); + }; +} \ No newline at end of file diff --git a/shared/helpers/Serialization.cpp b/shared/helpers/Serialization.cpp index 7f16b8d2..b990d73b 100644 --- a/shared/helpers/Serialization.cpp +++ b/shared/helpers/Serialization.cpp @@ -108,7 +108,6 @@ alt::MValue V8Helpers::V8ToMValue(v8::Local val, bool allowFunction) auto v8Obj = val.As(); auto cls = V8Helpers::GetObjectClass(v8Obj); - //// if (v8Obj->InstanceOf(ctx, v8Vector3->JSValue(isolate, ctx)).ToChecked()) if(cls == V8Class::ObjectClass::VECTOR3) { v8::Local x, y, z; @@ -265,51 +264,41 @@ void V8Helpers::MValueArgsToV8(alt::MValueArgs args, std::vector ctx, v8::Local val) +static inline V8Helpers::RawValueType GetValueType(v8::Local ctx, v8::Local val) { V8ResourceImpl* resource = V8ResourceImpl::Get(ctx); bool result; - if(val->IsSharedArrayBuffer() || val->IsPromise() || val->IsProxy()) return RawValueType::INVALID; + if(val->IsSharedArrayBuffer() || val->IsPromise() || val->IsProxy()) return V8Helpers::RawValueType::INVALID; if(val->InstanceOf(ctx, v8BaseObject.JSValue(ctx->GetIsolate(), ctx)).To(&result) && result) { V8Entity* entity = V8Entity::Get(val); - if(!entity) return RawValueType::INVALID; + if(!entity) return V8Helpers::RawValueType::INVALID; alt::IBaseObject* object = entity->GetHandle(); - if(!object) return RawValueType::INVALID; - return RawValueType::BASEOBJECT; + if(!object) return V8Helpers::RawValueType::INVALID; + return V8Helpers::RawValueType::BASEOBJECT; } if(val->IsObject()) { switch(V8Helpers::GetObjectClass(val.As())) { - case V8Class::ObjectClass::VECTOR3: return RawValueType::VECTOR3; - case V8Class::ObjectClass::VECTOR2: return RawValueType::VECTOR2; - case V8Class::ObjectClass::RGBA: return RawValueType::RGBA; + case V8Class::ObjectClass::VECTOR3: return V8Helpers::RawValueType::VECTOR3; + case V8Class::ObjectClass::VECTOR2: return V8Helpers::RawValueType::VECTOR2; + case V8Class::ObjectClass::RGBA: return V8Helpers::RawValueType::RGBA; } } - return RawValueType::GENERIC; + return V8Helpers::RawValueType::GENERIC; } -static inline bool WriteRawValue(v8::Local ctx, v8::ValueSerializer& serializer, RawValueType type, v8::Local val) +static inline bool WriteRawValue(v8::Local ctx, v8::ValueSerializer& serializer, V8Helpers::RawValueType type, v8::Local val) { CProfiler::Sample _("WriteRawValue", true); serializer.WriteRawBytes(&type, sizeof(uint8_t)); switch(type) { - case RawValueType::BASEOBJECT: + case V8Helpers::RawValueType::BASEOBJECT: { V8Entity* entity = V8Entity::Get(val); if(!entity) return false; @@ -340,7 +329,7 @@ static inline bool WriteRawValue(v8::Local ctx, v8::ValueSerializer serializer.WriteRawBytes(&remote, sizeof(remote)); break; } - case RawValueType::VECTOR3: + case V8Helpers::RawValueType::VECTOR3: { alt::Vector3f vec; if(!V8Helpers::SafeToVector3(val, ctx, vec)) return false; @@ -352,7 +341,7 @@ static inline bool WriteRawValue(v8::Local ctx, v8::ValueSerializer serializer.WriteRawBytes(&z, sizeof(float)); break; } - case RawValueType::VECTOR2: + case V8Helpers::RawValueType::VECTOR2: { alt::Vector2f vec; if(!V8Helpers::SafeToVector2(val, ctx, vec)) return false; @@ -362,7 +351,7 @@ static inline bool WriteRawValue(v8::Local ctx, v8::ValueSerializer serializer.WriteRawBytes(&y, sizeof(float)); break; } - case RawValueType::RGBA: + case V8Helpers::RawValueType::RGBA: { alt::RGBA rgba; if(!V8Helpers::SafeToRGBA(val, ctx, rgba)) return false; @@ -382,13 +371,13 @@ static inline v8::MaybeLocal ReadRawValue(v8::Local ctx v8::Isolate* isolate = ctx->GetIsolate(); V8ResourceImpl* resource = V8ResourceImpl::Get(ctx); - RawValueType* typePtr; + V8Helpers::RawValueType* typePtr; if(!deserializer.ReadRawBytes(sizeof(uint8_t), (const void**)&typePtr)) return v8::MaybeLocal(); - RawValueType type = *typePtr; + V8Helpers::RawValueType type = *typePtr; switch(type) { - case RawValueType::BASEOBJECT: + case V8Helpers::RawValueType::BASEOBJECT: { uint32_t* id; alt::IBaseObject::Type* type; @@ -415,7 +404,7 @@ static inline v8::MaybeLocal ReadRawValue(v8::Local ctx if(!object) return v8::MaybeLocal(); return V8ResourceImpl::Get(ctx)->GetOrCreateEntity(object, "BaseObject")->GetJSVal(isolate); } - case RawValueType::VECTOR3: + case V8Helpers::RawValueType::VECTOR3: { float* x; float* y; @@ -425,14 +414,14 @@ static inline v8::MaybeLocal ReadRawValue(v8::Local ctx return v8::MaybeLocal(); return resource->CreateVector3({ *x, *y, *z }).As(); } - case RawValueType::VECTOR2: + case V8Helpers::RawValueType::VECTOR2: { float* x; float* y; if(!deserializer.ReadRawBytes(sizeof(float), (const void**)&x) || !deserializer.ReadRawBytes(sizeof(float), (const void**)&y)) return v8::MaybeLocal(); return resource->CreateVector2({ *x, *y }).As(); } - case RawValueType::RGBA: + case V8Helpers::RawValueType::RGBA: { uint8_t* r; uint8_t* g; @@ -472,8 +461,8 @@ class WriteDelegate : public v8::ValueSerializer::Delegate v8::Maybe WriteHostObject(v8::Isolate* isolate, v8::Local object) override { v8::Local ctx = isolate->GetEnteredOrMicrotaskContext(); - RawValueType type = GetValueType(ctx, object); - if(type == RawValueType::INVALID) return v8::Nothing(); + V8Helpers::RawValueType type = GetValueType(ctx, object); + if(type == V8Helpers::RawValueType::INVALID) return v8::Nothing(); bool result = WriteRawValue(ctx, *serializer, type, object); if(!result) { @@ -512,8 +501,8 @@ alt::MValueByteArray V8Helpers::V8ToRawBytes(v8::Local val) v8::Isolate* isolate = v8::Isolate::GetCurrent(); v8::Local ctx = isolate->GetEnteredOrMicrotaskContext(); - RawValueType type = GetValueType(ctx, val); - if(type == RawValueType::INVALID) return alt::MValueByteArray(); + V8Helpers::RawValueType type = GetValueType(ctx, val); + if(type == V8Helpers::RawValueType::INVALID) return alt::MValueByteArray(); v8::ValueSerializer serializer(isolate, &delegate); delegate.SetSerializer(&serializer); @@ -528,9 +517,9 @@ alt::MValueByteArray V8Helpers::V8ToRawBytes(v8::Local val) if(!serializer.WriteValue(ctx, val).To(&result) || !result) return alt::MValueByteArray(); std::pair serialized = serializer.Release(); - auto mvArray = alt::ICore::Instance().CreateMValueByteArray(serialized.first, serialized.second); - - delegate.FreeBufferMemory(serialized.first); + auto mvArray = alt::ICore::Instance().CreateMValueByteArray(serialized.first, serialized.second); + + delegate.FreeBufferMemory(serialized.first); return mvArray; } diff --git a/shared/helpers/Serialization.h b/shared/helpers/Serialization.h index d338c6c5..cd2a07ba 100644 --- a/shared/helpers/Serialization.h +++ b/shared/helpers/Serialization.h @@ -5,6 +5,16 @@ namespace V8Helpers { + enum class RawValueType : uint8_t + { + INVALID, + GENERIC, + BASEOBJECT, + VECTOR3, + VECTOR2, + RGBA + }; + alt::MValue V8ToMValue(v8::Local val, bool allowFunction = true); v8::Local MValueToV8(alt::MValueConst val); void MValueArgsToV8(alt::MValueArgs args, std::vector>& v8Args); diff --git a/tools/convert-bindings.js b/tools/convert-bindings.js index f47eb5a3..da100a67 100644 --- a/tools/convert-bindings.js +++ b/tools/convert-bindings.js @@ -1,100 +1,162 @@ // clang-format off +// Generates the JSBindings.h file, which contains all the JS bindings for the server and client +// Usage: node tools/generate-bindings.js [basePath] [scope=shared|client|server] + const fs = require("fs").promises; +const { constants } = require("fs"); const pathUtil = require("path"); +const crypto = require("crypto"); // Base path should point to the main directory of the repo -if(process.argv.length < 3) { +if (process.argv.length < 3) { showError("Missing 'basePath' argument"); showUsage(); process.exit(1); } const basePath = process.argv[2]; -if(process.argv.length < 4) { - showError("Missing 'scope' argument"); - showUsage(); - process.exit(1); -} -const scope = process.argv[3]; -if(scope !== "SHARED" && scope !== "CLIENT" && scope !== "SERVER") { - showError("Invalid value for 'scope' argument, allowed values: ['SHARED', 'CLIENT', 'SERVER']"); - showUsage(); - process.exit(1); -} // Paths to search for JS bindings const paths = [ - { path: "shared/bindings/", scope: "SHARED" }, - { path: "client/src/bindings/", scope: "CLIENT" }, - { path: "server/src/bindings/", scope: "SERVER" } + { path: "client/src/bindings/js/", scope: "client" }, + { path: "server/src/bindings/js/", scope: "server" }, + { path: "shared/bindings/js/", scope: "shared" } ]; // Full output file -const resultTemplate = `// !!! THIS FILE WAS AUTOMATICALLY GENERATED (ON {Date}), DO NOT EDIT MANUALLY !!! -#pragma once -#include +const resultTemplate = `// !!! THIS FILE WAS AUTOMATICALLY GENERATED (ON {DATE}), DO NOT EDIT MANUALLY !!! +#include "helpers/JSBindings.h" -namespace JSBindings { - static std::string GetBindingsCode() +namespace js { + std::unordered_map js::Binding::__bindings = { - static constexpr char code[] = { {BindingsCode},'\\0' }; - return code; - } + {BINDINGS_LIST} + }; } `; +// Template for each binding item in the bindings map +const bindingTemplate = `{ "{BINDING_NAME}", js::Binding{ "{BINDING_NAME}", js::Binding::Scope::{BINDING_SCOPE}, { {BINDING_SRC} } } }`; + // Result bindings output path -const outputPath = "shared/JSBindings.h"; +const outputPath = "shared/CompiledBindings.cpp"; +const hashesOutputPath = "shared/bindings-hashes.json"; + +(async () => { + const fileHashes = {}; + const previousHashes = {}; + let anyHashChanged = false; + + const hashesOutputPathResolved = resolvePath(hashesOutputPath); + const outputPathResolved = resolvePath(outputPath); + + await fs.mkdir(resolvePath("shared"), { recursive: true }); + if ((await doesFileExist(outputPathResolved)) && (await doesFileExist(hashesOutputPathResolved))) { + const hashesStr = await fs.readFile(hashesOutputPathResolved, "utf8"); + Object.assign(previousHashes, JSON.parse(hashesStr)); + showLog("Loaded previous bindings hashes"); + } -(async() => { - // todo: support client/server only bindings - showLog("Generating bindings..."); const bindings = []; for (const { path, scope: pathScope } of paths) { - if(pathScope !== "SHARED" && pathScope !== scope) continue; - const bindingsPath = pathUtil.resolve(__dirname, basePath, path); - for await(const file of getBindingFiles(bindingsPath)) { - const fullFilePath = pathUtil.resolve(bindingsPath, file); + const bindingsPath = resolvePath(path); + for await (const file of getBindingFiles(bindingsPath)) { + const name = pathUtil.relative(bindingsPath, file).replace(/\\/g, "/"); + const bindingName = `${pathScope}/${name}`; // Generate the binding data - const binding = await generateBinding(fullFilePath); - bindings.push(binding); - showLog(`Generated bindings for: ${file}`); + const src = await fs.readFile(file, "utf8"); + bindings.push({ + name: bindingName, + src: getBindingCodeChars(src, name === "bootstrap.js"), + scope: pathScope.toUpperCase() + }); + // Store hash + fileHashes[bindingName] = getHash(src); + if (fileHashes[bindingName] != previousHashes[bindingName]) anyHashChanged = true; + showLog(`Generated bindings for: ${pathUtil.relative(`${__dirname}/..`, file).replace(/\\/g, "/")}`); } } - const fullBindingsCode = bindings.flat(); - const outputStr = resultTemplate - .replace("{BindingsCode}", fullBindingsCode.toString()) - .replace("{Date}", `${getDate()} ${getTime()}`); - await fs.writeFile(pathUtil.resolve(__dirname, basePath, outputPath), outputStr); + + if (!anyHashChanged) { + showLog("No bindings changed, skipping writing bindings result"); + return; + } + + // Generate data for the bindings map + let bindingsList = ""; + for (let i = 0; i < bindings.length; i++) { + const binding = bindings[i]; + const bindingStr = bindingTemplate + .replace(/\{BINDING_NAME\}/g, binding.name) + .replace("{BINDING_SCOPE}", binding.scope) + .replace("{BINDING_SRC}", binding.src); + bindingsList += bindingStr; + if (i < bindings.length - 1) bindingsList += ",\n "; + } + + // Store file hashes + await fs.writeFile(hashesOutputPathResolved, JSON.stringify(fileHashes)); + showLog(`Wrote bindings hashes to file: ${hashesOutputPath}`); + + const outputStr = resultTemplate.replace("{DATE}", `${getDate()} ${getTime()}`).replace("{BINDINGS_LIST}", bindingsList); + await fs.writeFile(outputPathResolved, outputStr); showLog(`Wrote bindings result to file: ${outputPath}`); })(); -async function generateBinding(path) { - const fileContent = await fs.readFile(path, "utf8"); - // Convert the whole file content to a char code array - const chars = fileContent.split("").map((char) => char.charCodeAt(0)); - return chars; -} - // Recursively gets all binding files in the directory, returns an async iterator async function* getBindingFiles(dir) { const items = await fs.readdir(dir, { withFileTypes: true }); for (const item of items) { const path = pathUtil.resolve(dir, item.name); - if(item.isDirectory()) yield* getBindingFiles(path); - if(!path.endsWith(".js")) continue; + if (item.isDirectory()) yield* getBindingFiles(path); + if (!path.endsWith(".js")) continue; else yield path; } } +/** + * @param {string} src + */ +function getBindingCodeChars(src, shouldSkipAddingConsts) { + // These consts have to be added so the bindings work at runtime, as the globals are removed after loading the bindings + let code = src; + if (!shouldSkipAddingConsts && !code.includes("const alt =")) code = `const alt = __alt;\n${code}`; + if (!shouldSkipAddingConsts && !code.includes("const cppBindings =")) code = `const cppBindings = __cppBindings;\n${code}`; + const chars = code.split("").map((char) => char.charCodeAt(0)); + return chars.toString(); +} + +function getHash(str) { + const hash = crypto.createHash("sha256"); + hash.update(str); + return hash.digest("hex"); +} + +async function doesFileExist(path) { + try { + await fs.access(path, constants.F_OK); + return true; + } catch (e) { + return false; + } +} + +function resolvePath(path) { + return pathUtil.resolve(__dirname, basePath, path); +} + function getDate() { const date = new Date(); - const day = date.getDate(), month = date.getMonth() + 1, year = date.getFullYear(); + const day = date.getDate(), + month = date.getMonth() + 1, + year = date.getFullYear(); return `${day < 10 ? `0${day}` : day}/${month < 10 ? `0${month}` : month}/${year}`; } function getTime() { const date = new Date(); - const hours = date.getHours(), minutes = date.getMinutes(), seconds = date.getSeconds(); + const hours = date.getHours(), + minutes = date.getMinutes(), + seconds = date.getSeconds(); return `${hours < 10 ? `0${hours}` : hours}:${minutes < 10 ? `0${minutes}` : minutes}:${seconds < 10 ? `0${seconds}` : seconds}`; } @@ -107,7 +169,7 @@ function showError(...args) { } function showUsage() { - showLog("Usage: convert-bindings.js "); + showLog("Usage: generate-bindings.js "); showLog(": Path to the base of the repository"); - showLog(": 'SHARED' includes only shared bindings, 'CLIENT' shared and client bindings, 'SERVER' shared and server bindings"); -} + showLog(": Scope [server|client|shared]"); +} \ No newline at end of file diff --git a/tools/enums-transpiler.js b/tools/enums-transpiler.js index 88ebb239..b3c7b1e5 100644 --- a/tools/enums-transpiler.js +++ b/tools/enums-transpiler.js @@ -156,14 +156,14 @@ const modules = [ } // Result bindings output path - const outputPath = "shared/JSEnums.h"; + const outputPath = "shared/CompiledEnums.h"; // Full output file const resultTemplate = `// !!! THIS FILE WAS AUTOMATICALLY GENERATED (ON {Date}), DO NOT EDIT MANUALLY !!! #pragma once #include -namespace JSEnums { +namespace CompiledEnums { static std::string GetBindingsCode() { static constexpr char code[] = { {BindingsCode},'\\0' }; From 5bbdeb1d85252440964bda1cba060d66b7f2b6a0 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Mon, 27 Jan 2025 10:31:09 +0100 Subject: [PATCH 03/22] ALTV-659: Move hash method to js --- shared/bindings/BindingsMain.cpp | 16 ++++---------- shared/bindings/js/utils.js | 36 +++++++++++++++++++++++++++----- shared/helpers/BindingHandler.h | 3 +++ 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/shared/bindings/BindingsMain.cpp b/shared/bindings/BindingsMain.cpp index 909082c7..d7e529a3 100644 --- a/shared/bindings/BindingsMain.cpp +++ b/shared/bindings/BindingsMain.cpp @@ -5,16 +5,6 @@ #include "../V8Module.h" #include "../CompiledEnums.h" -static void HashCb(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, str); - - V8_RETURN_UINT(alt::ICore::Instance().Hash(str)); -} - static void On(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -526,8 +516,6 @@ extern V8Module v8::Isolate* isolate = ctx->GetIsolate(); V8ResourceImpl* resource = V8ResourceImpl::Get(ctx); - V8Helpers::RegisterFunc(exports, "hash", &HashCb); - V8Helpers::RegisterFunc(exports, "log", &Log); V8Helpers::RegisterFunc(exports, "logWarning", &LogWarning); V8Helpers::RegisterFunc(exports, "logError", &LogError); @@ -574,6 +562,10 @@ extern V8Module if (resource->IsBindingsInitialized()) { + // Methods + V8Helpers::RegisterBindingExport(exports, "hash", js::BindingExport::HASH); + + // Classes V8Helpers::RegisterBindingExport(exports, "Vector2", js::BindingExport::VECTOR2_CLASS); V8Helpers::RegisterBindingExport(exports, "Vector3", js::BindingExport::VECTOR3_CLASS); V8Helpers::RegisterBindingExport(exports, "RGBA", js::BindingExport::RGBA_CLASS); diff --git a/shared/bindings/js/utils.js b/shared/bindings/js/utils.js index c54a9900..f19f45ee 100644 --- a/shared/bindings/js/utils.js +++ b/shared/bindings/js/utils.js @@ -170,24 +170,30 @@ alt.Utils.ConsoleCommand = class ConsoleCommand extends BaseUtility { } alt.Utils.AssertionError = class AssertionError extends Error {} -alt.Utils.assert = function(assertion, message) { + +export function assert(assertion, message) { if(!assertion) throw new alt.Utils.AssertionError(message ?? "Assertion failed"); } +alt.Utils.assert = assert; + // For convenience -function assertRGBA(val, message = "Expected RGBA") { +export function assertRGBA(val, message = "Expected RGBA") { return alt.Utils.assert( val && typeof val.r === "number" && typeof val.g === "number" && typeof val.b === "number" && typeof val.a === "number", message ); } -function assertVector3(val, message = "Expected Vector3") { +export function assertIsType(value, type, message) { + assert(typeof value === type, message); +} +export function assertVector3(val, message = "Expected Vector3") { return alt.Utils.assert( val && typeof val.x === "number" && typeof val.y === "number" && typeof val.z === "number", message ); } -function assertVector2(val, message = "Expected Vector2") { +export function assertVector2(val, message = "Expected Vector2") { return alt.Utils.assert( val && typeof val.x === "number" && typeof val.y === "number", message @@ -202,7 +208,7 @@ function assertDrawTextArgs(text, font, scale, color, outline, dropShadow, textA alt.Utils.assert(typeof dropShadow === "boolean", "Expected boolean as seventh argument"); alt.Utils.assert(typeof textAlign === "number", "Expected number as eighth argument"); } -function assertNotNaN(val, message = "Expected number") { +export function assertNotNaN(val, message = "Expected number") { alt.Utils.assert(!isNaN(val), message) } @@ -812,3 +818,23 @@ else { alt.Utils.getClosestVehicle = getClosestEntity(() => alt.Vehicle.all); alt.Utils.getClosestPlayer = getClosestEntity(() => alt.Player.all); } + +export function hash(str) { + assertIsType(str, "string", "Expected a string as first argument"); + + const string = str.toLowerCase(); + const length = string.length; + let hash = 0; + for (let i = 0; i < length; i++) { + hash += string.charCodeAt(i); + hash += hash << 10; + hash ^= hash >>> 6; + } + + hash += hash << 3; + hash ^= hash >>> 11; + hash += hash << 15; + + return hash >>> 0; // Convert to unsigned +} +cppBindings.registerExport(cppBindings.BindingExport.HASH, hash); \ No newline at end of file diff --git a/shared/helpers/BindingHandler.h b/shared/helpers/BindingHandler.h index a56f118f..616b1e33 100644 --- a/shared/helpers/BindingHandler.h +++ b/shared/helpers/BindingHandler.h @@ -9,6 +9,9 @@ namespace js { enum class BindingExport : uint8_t { + // Methods + HASH, + // Classes VECTOR3_CLASS, VECTOR2_CLASS, From 96659bcb21cf8f7ea296f24056961d92b6893c77 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Mon, 27 Jan 2025 14:48:16 +0100 Subject: [PATCH 04/22] ALTV-659: Remove unused js methods --- shared/helpers/JS.cpp | 20 +------- shared/helpers/JS.h | 117 ------------------------------------------ 2 files changed, 1 insertion(+), 136 deletions(-) diff --git a/shared/helpers/JS.cpp b/shared/helpers/JS.cpp index eb38bd86..93b0bc14 100644 --- a/shared/helpers/JS.cpp +++ b/shared/helpers/JS.cpp @@ -19,22 +19,4 @@ js::TemporaryGlobalExtension::TemporaryGlobalExtension(v8::Local _c : ctx(_ctx), name(_name) { ctx->Global()->Set(ctx, V8Helpers::JSValue(_name), WrapFunction(_callback)->GetFunction(ctx).ToLocalChecked()).Check(); -} - -bool js::Promise::Await() -{ - v8::Local promise = Get(); - while(true) - { - v8::Promise::PromiseState state = promise->State(); - - switch (state) - { - case v8::Promise::PromiseState::kPending: resource->OnTick(); break; - case v8::Promise::PromiseState::kFulfilled: return true; - case v8::Promise::PromiseState::kRejected: return false; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } -} +} \ No newline at end of file diff --git a/shared/helpers/JS.h b/shared/helpers/JS.h index 258d1c66..8d171748 100644 --- a/shared/helpers/JS.h +++ b/shared/helpers/JS.h @@ -75,121 +75,4 @@ namespace js v8::String::Utf8Value utf8Value(isolate, value); return std::string(*utf8Value, utf8Value.length()); } - - class TryCatch - { - v8::TryCatch tryCatch; - - void PrintError(bool skipLocation); - - public: - TryCatch() : tryCatch(v8::Isolate::GetCurrent()) {} - TryCatch(v8::Isolate* isolate) : tryCatch(isolate) {} - - bool Check(bool printError = true, bool skipLocation = false) - { - if(HasCaught()) - { - if(printError) PrintError(skipLocation); - tryCatch.Reset(); - return true; - } - return false; - } - - void ReThrow() - { - tryCatch.ReThrow(); - } - - bool HasCaught() const - { - return tryCatch.HasCaught(); - } - }; - - class PersistentValue - { - bool valid = true; - - protected: - v8::Isolate* isolate; - Persistent context; - - V8ResourceImpl* resource = nullptr; - - PersistentValue(bool _valid, v8::Local _context = v8::Isolate::GetCurrent()->GetEnteredOrMicrotaskContext()) - : valid(_valid), isolate(_context->GetIsolate()), context(_context->GetIsolate(), _context) - { - } - - public: - bool IsValid() const - { - return valid; - } - - v8::Isolate* GetIsolate() const - { - return isolate; - } - - v8::Local GetContext() const - { - return context.Get(isolate); - } - - V8ResourceImpl* GetResource(); - }; - - class Promise : public PersistentValue - { - friend class V8ResourceImpl; - - public: - using V8Type = v8::Promise::Resolver; - - private: - Persistent resolver; - mutable Persistent promise; - V8ResourceImpl* resource; - bool owned; - - Promise(V8ResourceImpl* _resource) - : PersistentValue(true), resolver(v8::Isolate::GetCurrent(), v8::Promise::Resolver::New(GetContext()).ToLocalChecked()), resource(_resource), owned(true) - { - } - - public: - Promise() : PersistentValue(true), resolver(v8::Isolate::GetCurrent(), v8::Promise::Resolver::New(GetContext()).ToLocalChecked()), owned(false) {} - Promise(v8::Local _promise) : PersistentValue(!_promise.IsEmpty()), promise(v8::Isolate::GetCurrent(), _promise), owned(false) {} - ~Promise(); - - v8::Local Get() const - { - if(!HasPromise() && HasResolver()) promise.Reset(v8::Isolate::GetCurrent(), GetResolver()->GetPromise()); - return promise.Get(v8::Isolate::GetCurrent()); - } - - v8::Local GetResolver() const - { - return resolver.Get(v8::Isolate::GetCurrent()); - } - - bool HasResolver() const - { - return !resolver.IsEmpty(); - } - bool HasPromise() const - { - return !promise.IsEmpty(); - } - - v8::Promise::PromiseState State() - { - return Get()->State(); - } - - bool Await(); - }; } \ No newline at end of file From 787f463b793fb374a0dde9fdab5182020d102459 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Mon, 27 Jan 2025 14:48:59 +0100 Subject: [PATCH 05/22] ALTV-659: Added dynamic property handler --- shared/helpers/BindHelpers.h | 90 +++++++++++++++++++++++++++++ shared/helpers/Convert.h | 20 +++++++ shared/helpers/JSTemplate.h | 108 +++++++++++++++++++++++++++++++++++ shared/helpers/Macros.h | 1 + 4 files changed, 219 insertions(+) create mode 100644 shared/helpers/JSTemplate.h diff --git a/shared/helpers/BindHelpers.h b/shared/helpers/BindHelpers.h index dcfd8ae3..f6d91d8c 100644 --- a/shared/helpers/BindHelpers.h +++ b/shared/helpers/BindHelpers.h @@ -3,6 +3,8 @@ #include "V8Helpers.h" #include "V8ResourceImpl.h" +#include "helpers/JSTemplate.h" + #define V8_CALL_GETTER(type, req, retn) \ template \ static inline void CallGetter(const v8::PropertyCallbackInfo& info, T* _this, type (T::*getter)() const) \ @@ -114,6 +116,94 @@ namespace V8Helpers } } // namespace detail + inline static void DynamicPropertyGetterHandler(v8::Local property, const v8::PropertyCallbackInfo& info) + { + js::internal::DynamicPropertyGetterContext ctx{ info, property }; + v8::Local parent = info.This()->GetInternalField(0).As(); + ctx.SetParent(parent); + + auto data = static_cast(info.Data().As()->Value()); + if (data->getter == nullptr) return; + data->getter(ctx); + } + + inline static void DynamicPropertySetterHandler(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info) + { + js::internal::DynamicPropertySetterContext ctx{ info, property, value }; + v8::Local parent = info.This()->GetInternalField(0).As(); + ctx.SetParent(parent); + + auto data = static_cast(info.Data().As()->Value()); + if (data->setter == nullptr) return; + data->setter(ctx); + V8_RETURN(value); + } + + inline static void DynamicPropertyDeleterHandler(v8::Local property, const v8::PropertyCallbackInfo& info) + { + js::internal::DynamicPropertyDeleterContext ctx{ info, property }; + v8::Local parent = info.This()->GetInternalField(0).As(); + ctx.SetParent(parent); + + auto data = static_cast(info.Data().As()->Value()); + if (data->deleter == nullptr) return; + data->deleter(ctx); + } + + inline static void DynamicPropertyEnumeratorHandler(const v8::PropertyCallbackInfo& info) + { + js::internal::DynamicPropertyEnumeratorContext ctx{ info }; + v8::Local parent = info.This()->GetInternalField(0).As(); + ctx.SetParent(parent); + + auto data = static_cast(info.Data().As()->Value()); + if (data->enumerator == nullptr) return; + data->enumerator(ctx); + } + + inline static void DynamicPropertyLazyHandler(v8::Local property, const v8::PropertyCallbackInfo& info) + { + js::internal::DynamicPropertyGetterContext ctx{ info, property }; + + js::DynamicPropertyData* data = static_cast(info.Data().As()->Value()); + v8::NamedPropertyHandlerConfiguration config { + DynamicPropertyGetterHandler, + data->setter ? DynamicPropertySetterHandler : nullptr, + nullptr, + data->deleter ? DynamicPropertyDeleterHandler : nullptr, + data->enumerator ? DynamicPropertyEnumeratorHandler : nullptr, + nullptr, nullptr, + v8::External::New(ctx.GetIsolate(), data), + v8::PropertyHandlerFlags::kOnlyInterceptStrings + }; + + v8::Local context = ctx.GetIsolate()->GetCurrentContext(); + v8::Local tpl = v8::ObjectTemplate::New(ctx.GetIsolate()); + tpl->SetInternalFieldCount(2); + tpl->SetHandler(config); + v8::Local obj = tpl->NewInstance(context).ToLocalChecked(); + obj->SetInternalField(0, info.This()); + V8_RETURN(obj); + } + + // Property returns an object that will call the specified handlers + inline void SetDynamicProperty(v8::Isolate* isolate, v8::Local tpl, + const std::string& name, + js::internal::DynamicPropertyGetter getter, + js::internal::DynamicPropertySetter setter = nullptr, + js::internal::DynamicPropertyDeleter deleter = nullptr, + js::internal::DynamicPropertyEnumerator enumerator = nullptr) + { + js::DynamicPropertyData* data = new js::DynamicPropertyData(getter, setter, deleter, enumerator); + if(getter) data->getter = getter; + if(setter) data->setter = setter; + if(deleter) data->deleter = deleter; + if(enumerator) data->enumerator = enumerator; + + tpl->InstanceTemplate()->SetLazyDataProperty( + V8Helpers::JSValue(name), DynamicPropertyLazyHandler, v8::External::New(isolate, data), (v8::PropertyAttribute)(v8::ReadOnly | v8::DontEnum)); + } + template inline void SetAccessor(v8::Isolate* isolate, v8::Local tpl, const char* name) { diff --git a/shared/helpers/Convert.h b/shared/helpers/Convert.h index 002467be..e0697114 100644 --- a/shared/helpers/Convert.h +++ b/shared/helpers/Convert.h @@ -49,6 +49,26 @@ namespace V8Helpers if(val.size() == 0) return v8::String::Empty(v8::Isolate::GetCurrent()); return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), val.data(), v8::NewStringType::kNormal, (int)val.size()).ToLocalChecked(); } + inline v8::Local JSValue(std::vector val) + { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + // Create a new V8 Array + v8::Local result = v8::Array::New(isolate, static_cast(val.size())); + + // Populate the V8 Array with elements from the vector + for (size_t i = 0; i < val.size(); ++i) { + // Convert each std::string to a V8 String + v8::Local v8Str = v8::String::NewFromUtf8( + isolate, + val[i].c_str(), + v8::NewStringType::kNormal + ).ToLocalChecked(); + + // Set the V8 String in the array + result->Set(isolate->GetCurrentContext(), static_cast(i), v8Str).Check(); + } + return result; + } inline v8::Local JSValue(const char* val) { return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), val).ToLocalChecked(); diff --git a/shared/helpers/JSTemplate.h b/shared/helpers/JSTemplate.h new file mode 100644 index 00000000..568339f6 --- /dev/null +++ b/shared/helpers/JSTemplate.h @@ -0,0 +1,108 @@ +#pragma once + +#include "v8.h" +#include "cpp-sdk/SDK.h" + +#include "V8Helpers.h" + +namespace js +{ + template + constexpr bool IsJSValueConvertible = false; + template + constexpr bool IsJSValueConvertible()))>> = true; + + template + class DynamicPropertyContext : public v8::PropertyCallbackInfo + { + std::string property; + v8::Local dataObj = v8::Local(); + v8::Local parent; // Used for dynamic properties + alt::IBaseObject* parentObj = nullptr; // + + public: + template + explicit DynamicPropertyContext(const v8::PropertyCallbackInfo& _info) + : v8::PropertyCallbackInfo(_info) + { + } + template + DynamicPropertyContext(const v8::PropertyCallbackInfo& _info, v8::Local _property) + : v8::PropertyCallbackInfo(_info), property(V8Helpers::CppValue(_property.As())) + { + } + template + DynamicPropertyContext(const v8::PropertyCallbackInfo& _info, v8::Local _property, v8::Local value) + : v8::PropertyCallbackInfo(_info), property(V8Helpers::CppValue(_property.As())), dataObj(value) + { + } + + void SetParent(v8::Local _parent) + { + parent = _parent; + } + bool CheckParent() + { + if(GetParent() == nullptr) + { + V8Helpers::Throw(this->GetIsolate(), "Invalid parent base object"); + return false; + } + return true; + } + + template + T* GetParent() + { + if(parentObj) return dynamic_cast(parentObj); + std::optional object = std::make_optional(V8Entity::Get(parent.As())); + if(!object.has_value()) return nullptr; + parentObj = object.value()->GetHandle(); + return dynamic_cast(parentObj); + } + + v8::Local GetValue() + { + return dataObj; + } + + const std::string& GetProperty() + { + return property; + } + + void Return(v8::Local value) + { + this->GetReturnValue().Set(value); + } + }; + + namespace internal + { + using DynamicPropertyGetterContext = DynamicPropertyContext; + using DynamicPropertySetterContext = DynamicPropertyContext; + using DynamicPropertyDeleterContext = DynamicPropertyContext; + using DynamicPropertyEnumeratorContext = DynamicPropertyContext; + + using DynamicPropertyGetter = void (*)(DynamicPropertyGetterContext&); + using DynamicPropertySetter = void (*)(DynamicPropertySetterContext&); + using DynamicPropertyDeleter = void (*)(DynamicPropertyDeleterContext&); + using DynamicPropertyEnumerator = void (*)(DynamicPropertyEnumeratorContext&); + } + + struct DynamicPropertyData + { + internal::DynamicPropertyGetter getter; + internal::DynamicPropertySetter setter; + internal::DynamicPropertyDeleter deleter; + internal::DynamicPropertyEnumerator enumerator; + + DynamicPropertyData(internal::DynamicPropertyGetter _getter, + internal::DynamicPropertySetter _setter, + internal::DynamicPropertyDeleter _deleter, + internal::DynamicPropertyEnumerator _enumerator) + : getter(_getter), setter(_setter), deleter(_deleter), enumerator(_enumerator) + { + } + }; +} \ No newline at end of file diff --git a/shared/helpers/Macros.h b/shared/helpers/Macros.h index 2f67a2d3..a28f6da3 100644 --- a/shared/helpers/Macros.h +++ b/shared/helpers/Macros.h @@ -340,6 +340,7 @@ #define V8_RETURN(val) info.GetReturnValue().Set(val) #define V8_RETURN_NULL() info.GetReturnValue().SetNull() +#define V8_RETURN_UNDEFINED() info.GetReturnValue().SetUndefined() #define V8_RETURN_BOOLEAN(val) V8_RETURN(val) #define V8_RETURN_INT(val) V8_RETURN(static_cast(val)) #define V8_RETURN_UINT(val) V8_RETURN(static_cast(val)) From 7605c564fafea9690cace4161ce76426830f5659 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Mon, 27 Jan 2025 14:49:27 +0100 Subject: [PATCH 06/22] ALTV-659: Move synced stream meta from cpp to js --- server/src/bindings/js/classes/entity.js | 33 +++++ shared/bindings/Entity.cpp | 102 +++++---------- shared/bindings/js/classes/sharedEntity.js | 13 ++ shared/bindings/js/utils.js | 4 + shared/bindings/js/utils/classes.js | 143 +++++++++++++++++++++ 5 files changed, 224 insertions(+), 71 deletions(-) create mode 100644 server/src/bindings/js/classes/entity.js create mode 100644 shared/bindings/js/classes/sharedEntity.js create mode 100644 shared/bindings/js/utils/classes.js diff --git a/server/src/bindings/js/classes/entity.js b/server/src/bindings/js/classes/entity.js new file mode 100644 index 00000000..1cd8ab92 --- /dev/null +++ b/server/src/bindings/js/classes/entity.js @@ -0,0 +1,33 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { isObject } = requireBinding("shared/utils.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); + +class Entity { + setSyncedMeta(key, value) { + if (isObject(key)) { + this.setMultipleSyncedMetaData(key); + return; + } + + this.syncedMeta[key] = value; + } + + deleteSyncedMeta(key) { + delete this.syncedMeta[key]; + } + + setStreamSyncedMeta(key, value) { + if (isObject(key)) { + this.setMultipleStreamSyncedMetaData(key); + return; + } + + this.streamSyncedMeta[key] = value; + } + + deleteStreamSyncedMeta(key) { + delete this.streamSyncedMeta[key]; + } +} + +extendClassWithProperties(alt.Entity, null, Entity, SharedEntity); \ No newline at end of file diff --git a/shared/bindings/Entity.cpp b/shared/bindings/Entity.cpp index 3450903f..e5516739 100644 --- a/shared/bindings/Entity.cpp +++ b/shared/bindings/Entity.cpp @@ -8,48 +8,47 @@ using namespace alt; -static void HasStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IEntity); - - V8_RETURN_BOOLEAN(ent->HasStreamSyncedMetaData(key)); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetStreamSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetStreamSyncedMetaDataKeys()); + ctx.Return(value); +} - V8_GET_THIS_BASE_OBJECT(ent, alt::IEntity); +#ifdef ALT_SERVER_API - V8_RETURN_MVALUE(ent->GetStreamSyncedMetaData(key)); +static void StreamSyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetStreamSyncedMetaData(ctx.GetProperty(), value); } -static void GetStreamSyncedMetaDataKeys(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IEntity); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); - const std::vector list = ent->GetStreamSyncedMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) + if(!obj->HasStreamSyncedMetaData(ctx.GetProperty())) { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); + ctx.Return(V8Helpers::JSValue(false)); + return; } - V8_RETURN(arr); + obj->DeleteStreamSyncedMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } -#ifdef ALT_SERVER_API - static void ModelGetter(v8::Local, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); @@ -74,45 +73,6 @@ static void ModelSetter(v8::Local, v8::Local val, const v } } -static void SetStreamSyncedMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IEntity); - - if (info.Length() == 2) - { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - ent->SetStreamSyncedMetaData(key, value); - } - else if (info.Length() == 1 && info[0]->IsObject()) - { - auto dict = V8Helpers::CppValue>(info[0].As()); - std::unordered_map values; - - if (dict.has_value()) - { - for (auto& [key, value] : dict.value()) - values[key] = V8Helpers::V8ToMValue(value); - } - - ent->SetMultipleStreamSyncedMetaData(values); - } -} - -static void DeleteStreamSyncedMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IEntity); - - ent->DeleteStreamSyncedMetaData(key); -} - static void SetNetOwner(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -299,19 +259,19 @@ extern V8Class v8Entity("Entity", V8Helpers::SetAccessor(isolate, tpl, "netOwner"); - V8Helpers::SetMethod(isolate, tpl, "hasStreamSyncedMeta", HasStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getStreamSyncedMeta", GetStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getStreamSyncedMetaKeys", GetStreamSyncedMetaDataKeys); V8Helpers::SetAccessor(isolate, tpl, "frozen"); + // Client will share only get and enumerate + V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, nullptr, nullptr, StreamSyncedMetaEnumerator); + #ifdef ALT_SERVER_API V8Helpers::SetAccessor(isolate, tpl, "rot"); V8Helpers::SetAccessor(isolate, tpl, "model", &ModelGetter, &ModelSetter); V8Helpers::SetAccessor(isolate, tpl, "visible"); V8Helpers::SetAccessor(isolate, tpl, "streamed"); - V8Helpers::SetMethod(isolate, tpl, "setStreamSyncedMeta", SetStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteStreamSyncedMeta", DeleteStreamSyncedMeta); + // Server has also setter and deleter + V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, StreamSyncedMetaSetter, StreamSyncedMetaDeleter, StreamSyncedMetaEnumerator); V8Helpers::SetMethod(isolate, tpl, "setNetOwner", SetNetOwner); V8Helpers::SetMethod(isolate, tpl, "resetNetOwner", ResetNetOwner); diff --git a/shared/bindings/js/classes/sharedEntity.js b/shared/bindings/js/classes/sharedEntity.js new file mode 100644 index 00000000..b9e1ee7d --- /dev/null +++ b/shared/bindings/js/classes/sharedEntity.js @@ -0,0 +1,13 @@ +export class SharedEntity { + hasStreamSyncedMeta(key) { + return this.streamSyncedMeta[key] !== undefined; + } + + getStreamSyncedMeta(key) { + return this.streamSyncedMeta[key]; + } + + getStreamSyncedMetaKeys() { + return Object.keys(this.streamSyncedMeta); + } +} \ No newline at end of file diff --git a/shared/bindings/js/utils.js b/shared/bindings/js/utils.js index f19f45ee..541e345d 100644 --- a/shared/bindings/js/utils.js +++ b/shared/bindings/js/utils.js @@ -819,6 +819,10 @@ else { alt.Utils.getClosestPlayer = getClosestEntity(() => alt.Player.all); } +export function isObject(value) { + return value !== null && typeof value === "object"; +} + export function hash(str) { assertIsType(str, "string", "Expected a string as first argument"); diff --git a/shared/bindings/js/utils/classes.js b/shared/bindings/js/utils/classes.js new file mode 100644 index 00000000..43744b5a --- /dev/null +++ b/shared/bindings/js/utils/classes.js @@ -0,0 +1,143 @@ +const { assertIsType } = requireBinding("shared/utils.js"); +requireBinding("shared/logging.js"); + +const defaultOptions = { + verbose: false, + + blacklist: { + nonStatic: ["constructor"], + static: ["prototype", "length", "name", "caller", "arguments"] + }, + + whitelist: { + nonStatic: [], + static: [] + } +}; + +function applyNonStaticProperties(baseClass, cls, options) { + let prot = cls.prototype; + while (prot !== Object.prototype) { + for (const prop of Object.getOwnPropertyNames(prot)) { + const baseDescriptor = Object.getOwnPropertyDescriptor(baseClass.prototype, prop); + const newDescriptor = Object.getOwnPropertyDescriptor(prot, prop); + + const isBlacklisted = options.blacklist.nonStatic.includes(prop); + const isWhitelisted = options.whitelist.nonStatic.includes(prop); + const canBeMerged = !isBlacklisted || isWhitelisted; + + if (canBeMerged && newDescriptor) { + let mergedDescriptor = baseDescriptor ? { ...baseDescriptor } : {}; + + ["get", "set", "value"].forEach((key) => { + if (key in newDescriptor && (!mergedDescriptor[key] || isWhitelisted)) { + if (options.verbose) { + alt.log(`~lr~Merged ${key} for ${prop} from ${prot.constructor.name} to ${baseClass.name}`); + } + mergedDescriptor[key] = newDescriptor[key]; + } + }); + + // Make data descriptors writable if they are not already. + if (!!mergedDescriptor.value) mergedDescriptor.writable = true; + + Object.defineProperty(baseClass.prototype, prop, mergedDescriptor); + + if (options.verbose) { + const action = baseDescriptor ? "Merged" : "Applied"; + alt.log(`~lb~${action} non-static property ${prop} from ${prot.constructor.name} to ${baseClass.name}`); + } + } + } + + prot = Object.getPrototypeOf(prot); + } +} + +function applyStaticProperties(baseClass, cls, options) { + for (const propKey of Object.getOwnPropertyNames(cls)) { + const doesPropertyExist = propKey in baseClass || propKey in baseClass.prototype; + const canBeOverriden = !options.blacklist.static.includes(propKey) || options.whitelist.static.includes(propKey); + + if (doesPropertyExist && !canBeOverriden) { + if (options.verbose) { + const reason = !canBeOverriden ? "blacklisted" : "already exists"; + alt.log(`~lb~Skipping static property ${propKey} in ${cls.name}: ${reason}`); + } + + continue; + } + + let descriptor = Object.getOwnPropertyDescriptor(cls, propKey); + if (descriptor) { + if (typeof descriptor.get === "function") { + descriptor = { ...descriptor, get: descriptor.get.bind(cls) }; + } + + Object.defineProperty(baseClass, propKey, descriptor); + + if (options.verbose) { + alt.log(`~lb~Applied static property ${propKey} in ${cls.name} to ${baseClass.name}`); + } + } + } +} + +/** + * @typedef {Object} Options + * @property {boolean} verbose - Log verbose informations. + * @property {Object} blacklist - Properties to exclude from extension. + * @property {string[]} blacklist.nonStatic + * @property {string[]} blacklist.static + * @property {Object} whitelist - Properties allowed to be overwritten. (This will bypass blacklist rules) + * @property {string[]} whitelist.nonStatic + * @property {string[]} whitelist.static + */ + +/** + * Extends a base class with properties from other classes. + * + * @param {Function} baseClass - The base class to extend. + * @param {Options | null} options + * @param {Function[]} classes - Classes whose properties should be added to the base class. + */ +export function extendClassWithProperties(baseClass, options, ...classes) { + assertIsType(baseClass, "function", `Expected class object, but got ${typeof baseClass}`); + + const _options = { + ...defaultOptions, + ...(options || {}), + blacklist: { + nonStatic: [...defaultOptions.blacklist.nonStatic, ...(options?.blacklist?.nonStatic || [])], + static: [...defaultOptions.blacklist.static, ...(options?.blacklist?.static || [])] + }, + whitelist: { + nonStatic: [...defaultOptions.whitelist.nonStatic, ...(options?.whitelist?.nonStatic || [])], + static: [...defaultOptions.whitelist.static, ...(options?.whitelist?.static || [])] + } + }; + + for (const cls of classes) { + applyNonStaticProperties(baseClass, cls, _options); + applyStaticProperties(baseClass, cls, _options); + } +} + +export function overrideLazyProperty(instance, propertyName, value) { + const descriptor = Object.getOwnPropertyDescriptor(instance, propertyName); + + if (!descriptor) { + alt.log(`~lr~Lazy Property ${propertyName} does not exist in ${instance.constructor.name} to override property`); + return; + } + + const newDescriptor = { ...descriptor }; + + if (typeof newDescriptor.get == "function") { + newDescriptor.get = () => value; + } else { + newDescriptor.value = value; + } + + Object.defineProperty(instance, propertyName, newDescriptor); +} \ No newline at end of file From f963fe032ae0b40513cde3f9e13e47acbfcc15a0 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Mon, 27 Jan 2025 16:10:55 +0100 Subject: [PATCH 07/22] ALTV-659: Move meta, syncedMeta and streamMeta functionality to js --- client/src/bindings/js/classes/baseObject.js | 4 + client/src/bindings/js/classes/checkpoint.js | 10 + client/src/bindings/js/classes/entity.js | 10 + .../src/bindings/js/classes/virtualEntity.js | 20 ++ .../bindings/js/classes/virtualEntityGroup.js | 31 +++ server/src/bindings/Checkpoint.cpp | 91 +++------ server/src/bindings/js/classes/baseObject.js | 4 + server/src/bindings/js/classes/checkpoint.js | 24 +++ .../src/bindings/js/classes/virtualEntity.js | 31 +++ .../bindings/js/classes/virtualEntityGroup.js | 5 + shared/bindings/BaseObject.cpp | 192 ++++-------------- shared/bindings/BindingsMain.cpp | 106 +++------- shared/bindings/Entity.cpp | 9 - shared/bindings/VirtualEntity.cpp | 173 ++++------------ shared/bindings/VirtualEntityGroup.cpp | 85 ++------ .../bindings/js/classes/sharedBaseObject.js | 40 ++++ shared/helpers/Bindings.cpp | 19 ++ shared/helpers/Bindings.h | 7 + shared/helpers/JSTemplate.h | 5 +- 19 files changed, 362 insertions(+), 504 deletions(-) create mode 100644 client/src/bindings/js/classes/baseObject.js create mode 100644 client/src/bindings/js/classes/checkpoint.js create mode 100644 client/src/bindings/js/classes/entity.js create mode 100644 client/src/bindings/js/classes/virtualEntity.js create mode 100644 client/src/bindings/js/classes/virtualEntityGroup.js create mode 100644 server/src/bindings/js/classes/baseObject.js create mode 100644 server/src/bindings/js/classes/checkpoint.js create mode 100644 server/src/bindings/js/classes/virtualEntity.js create mode 100644 server/src/bindings/js/classes/virtualEntityGroup.js create mode 100644 shared/bindings/js/classes/sharedBaseObject.js diff --git a/client/src/bindings/js/classes/baseObject.js b/client/src/bindings/js/classes/baseObject.js new file mode 100644 index 00000000..55a39d29 --- /dev/null +++ b/client/src/bindings/js/classes/baseObject.js @@ -0,0 +1,4 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); + +extendClassWithProperties(alt.BaseObject, null, SharedBaseObject); \ No newline at end of file diff --git a/client/src/bindings/js/classes/checkpoint.js b/client/src/bindings/js/classes/checkpoint.js new file mode 100644 index 00000000..8dc46c6c --- /dev/null +++ b/client/src/bindings/js/classes/checkpoint.js @@ -0,0 +1,10 @@ +const { assert } = requireBinding("shared/utils.js"); +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Checkpoint{ + static get count() { + return alt.Checkpoint.all.length; + } +} + +extendClassWithProperties(alt.Checkpoint, null, Checkpoint); \ No newline at end of file diff --git a/client/src/bindings/js/classes/entity.js b/client/src/bindings/js/classes/entity.js new file mode 100644 index 00000000..e7eb5256 --- /dev/null +++ b/client/src/bindings/js/classes/entity.js @@ -0,0 +1,10 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); + +class Entity { + get isSpawned() { + return this.scriptID != 0; + } +} + +extendClassWithProperties(alt.Entity, null, Entity, SharedEntity); \ No newline at end of file diff --git a/client/src/bindings/js/classes/virtualEntity.js b/client/src/bindings/js/classes/virtualEntity.js new file mode 100644 index 00000000..8d17d741 --- /dev/null +++ b/client/src/bindings/js/classes/virtualEntity.js @@ -0,0 +1,20 @@ +const { assert } = requireBinding("shared/utils.js"); +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); + +class VirtualEntity { + setStreamSyncedMeta(key, value) { + if (isObject(key)) { + this.setMultipleStreamSyncedMetaData(key); + return; + } + + this.streamSyncedMeta[key] = value; + } + + deleteStreamSyncedMeta(key) { + delete this.streamSyncedMeta[key]; + } +} + +extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedEntity); \ No newline at end of file diff --git a/client/src/bindings/js/classes/virtualEntityGroup.js b/client/src/bindings/js/classes/virtualEntityGroup.js new file mode 100644 index 00000000..e95d2ddb --- /dev/null +++ b/client/src/bindings/js/classes/virtualEntityGroup.js @@ -0,0 +1,31 @@ +const { assert } = requireBinding("shared/utils.js"); +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class VirtualEntityGroup { + hasMeta(key) { + return this.meta[key] !== undefined; + } + + getMeta(key) { + return this.meta[key]; + } + + getMetaKeys() { + return Object.keys(this.meta); + } + + setMeta(key, value) { + if (isObject(key)) { + this.setMultipleMetaData(key); + return; + } + + this.meta[key] = value; + } + + deleteMeta(key) { + delete this.meta[key]; + } +} + +extendClassWithProperties(alt.VirtualEntityGroup, null, VirtualEntityGroup); \ No newline at end of file diff --git a/server/src/bindings/Checkpoint.cpp b/server/src/bindings/Checkpoint.cpp index dbe41e14..51cd23e8 100644 --- a/server/src/bindings/Checkpoint.cpp +++ b/server/src/bindings/Checkpoint.cpp @@ -69,82 +69,43 @@ static void CountGetter(v8::Local name, const v8::PropertyCallbackIn V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::CHECKPOINT).size()); } -static void HasStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(checkpoint, alt::ICheckpoint); - - V8_RETURN_BOOLEAN(checkpoint->HasStreamSyncedMetaData(key)); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetStreamSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(checkpoint, alt::ICheckpoint); - - V8_RETURN_MVALUE(checkpoint->GetStreamSyncedMetaData(key)); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetStreamSyncedMetaDataKeys()); + ctx.Return(value); } -static void GetStreamSyncedMetaDataKeys(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(checkpoint, alt::ICheckpoint); - - const std::vector list = checkpoint->GetStreamSyncedMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) - { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); - } - - V8_RETURN(arr); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetStreamSyncedMetaData(ctx.GetProperty(), value); } -static void SetStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(checkpoint, alt::ICheckpoint); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); - if(info.Length() == 2) + if(!obj->HasStreamSyncedMetaData(ctx.GetProperty())) { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - checkpoint->SetStreamSyncedMetaData(key, value); - } - else if(info.Length() == 1 && info[0]->IsObject()) - { - auto dict = V8Helpers::CppValue>(info[0].As()); - std::unordered_map values; - - if(dict.has_value()) - { - for(auto& [key, value] : dict.value()) values[key] = V8Helpers::V8ToMValue(value); - } - - checkpoint->SetMultipleStreamSyncedMetaData(values); + ctx.Return(V8Helpers::JSValue(false)); + return; } -} - -static void DeleteStreamSyncedMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(checkpoint, alt::ICheckpoint); - checkpoint->DeleteStreamSyncedMetaData(key); + obj->DeleteStreamSyncedMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } static void StaticGetByID(const v8::FunctionCallbackInfo& info) @@ -180,9 +141,5 @@ extern V8Class v8Checkpoint("Checkpoint", V8Helpers::SetAccessor(isolate, tpl, "streamingDistance"); V8Helpers::SetAccessor(isolate, tpl, "visible"); - V8Helpers::SetMethod(isolate, tpl, "hasStreamSyncedMeta", HasStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getStreamSyncedMeta", GetStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getStreamSyncedMetaKeys", GetStreamSyncedMetaDataKeys); - V8Helpers::SetMethod(isolate, tpl, "setStreamSyncedMeta", SetStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteStreamSyncedMeta", DeleteStreamSyncedMeta); + V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, StreamSyncedMetaSetter, StreamSyncedMetaDeleter, StreamSyncedMetaEnumerator); }); diff --git a/server/src/bindings/js/classes/baseObject.js b/server/src/bindings/js/classes/baseObject.js new file mode 100644 index 00000000..55a39d29 --- /dev/null +++ b/server/src/bindings/js/classes/baseObject.js @@ -0,0 +1,4 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); + +extendClassWithProperties(alt.BaseObject, null, SharedBaseObject); \ No newline at end of file diff --git a/server/src/bindings/js/classes/checkpoint.js b/server/src/bindings/js/classes/checkpoint.js new file mode 100644 index 00000000..1a2ba590 --- /dev/null +++ b/server/src/bindings/js/classes/checkpoint.js @@ -0,0 +1,24 @@ +const { assert } = requireBinding("shared/utils.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Checkpoint { + static get count() { + return alt.Checkpoint.all.length; + } + + setStreamSyncedMeta(key, value) { + if (isObject(key)) { + this.setMultipleStreamSyncedMetaData(key); + return; + } + + this.streamSyncedMeta[key] = value; + } + + deleteStreamSyncedMeta(key) { + delete this.streamSyncedMeta[key]; + } +} + +extendClassWithProperties(alt.Checkpoint, null, Checkpoint, SharedEntity); diff --git a/server/src/bindings/js/classes/virtualEntity.js b/server/src/bindings/js/classes/virtualEntity.js new file mode 100644 index 00000000..d13e53b6 --- /dev/null +++ b/server/src/bindings/js/classes/virtualEntity.js @@ -0,0 +1,31 @@ +const { assert } = requireBinding("shared/utils.js"); +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); + +class VirtualEntity { + hasSyncedMeta(key) { + return this.syncedMeta[key] !== undefined; + } + + getSyncedMeta(key) { + return this.syncedMeta[key]; + } + + getSyncedMetaKeys() { + return Object.keys(this.syncedMeta); + } + + hasStreamSyncedMeta(key) { + return this.streamSyncedMeta[key] !== undefined; + } + + getStreamSyncedMeta(key) { + return this.streamSyncedMeta[key]; + } + + getStreamSyncedMetaKeys() { + return Object.keys(this.streamSyncedMeta); + } +} + +extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedBaseObject); \ No newline at end of file diff --git a/server/src/bindings/js/classes/virtualEntityGroup.js b/server/src/bindings/js/classes/virtualEntityGroup.js new file mode 100644 index 00000000..06d766f1 --- /dev/null +++ b/server/src/bindings/js/classes/virtualEntityGroup.js @@ -0,0 +1,5 @@ +const { assert } = requireBinding("shared/utils.js"); +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); + +extendClassWithProperties(alt.VirtualEntityGroup, null, SharedBaseObject); \ No newline at end of file diff --git a/shared/bindings/BaseObject.cpp b/shared/bindings/BaseObject.cpp index ad7fbb8e..6ba26969 100644 --- a/shared/bindings/BaseObject.cpp +++ b/shared/bindings/BaseObject.cpp @@ -7,53 +7,20 @@ using namespace alt; -static void HasSyncedMeta(const v8::FunctionCallbackInfo& info) +static void SyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IBaseObject); - - V8_RETURN_BOOLEAN(ent->HasSyncedMetaData(key)); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetSyncedMeta(const v8::FunctionCallbackInfo& info) +static void SyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IBaseObject); - - V8_RETURN_MVALUE(ent->GetSyncedMetaData(key)); -} - -static void GetSyncedMetaDataKeys(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IBaseObject); - - const std::vector list = ent->GetSyncedMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) - { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); - } - - V8_RETURN(arr); -} - -static void TypeGetter(v8::Local, const v8::PropertyCallbackInfo& info) -{ - V8_GET_ISOLATE(); - - V8_GET_THIS_BASE_OBJECT(obj, alt::IBaseObject); - - V8_RETURN_INT((uint32_t)obj->GetType()); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetSyncedMetaDataKeys()); + ctx.Return(value); } static void ValidGetter(v8::Local, const v8::PropertyCallbackInfo& info) @@ -65,82 +32,34 @@ static void ValidGetter(v8::Local, const v8::PropertyCallbackInfo& info) +static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(obj, alt::IBaseObject); - - V8_RETURN_BOOLEAN(obj->HasMetaData(key)); + auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetMeta(const v8::FunctionCallbackInfo& info) +static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(obj, alt::IBaseObject); - - V8_RETURN_MVALUE(obj->GetMetaData(key)); + alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); + alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); } -static void SetMeta(const v8::FunctionCallbackInfo& info) +static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(obj, alt::IBaseObject); - - if (info.Length() == 2) + if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - obj->SetMetaData(key, value); + ctx.Return(V8Helpers::JSValue(false)); + return; } - else if (info.Length() == 1 && info[0]->IsObject()) - { - auto dict = V8Helpers::CppValue>(info[0].As()); - std::unordered_map values; - - if(dict.has_value()) - { - for(auto& [key, value] : dict.value()) values[key] = V8Helpers::V8ToMValue(value); - } - obj->SetMultipleMetaData(values); - } + alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } -static void DeleteMeta(const v8::FunctionCallbackInfo& info) +static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(obj, alt::IBaseObject); - - obj->DeleteMetaData(key); -} - -static void GetMetaDataKeys(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(obj, alt::IBaseObject); - - const std::vector list = obj->GetMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) - { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); - } - - V8_RETURN(arr); + auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + ctx.Return(keys); } static void Destroy(const v8::FunctionCallbackInfo& info) @@ -164,43 +83,27 @@ static void StaticGetById(const v8::FunctionCallbackInfo& info) } #ifdef ALT_SERVER_API -static void SetSyncedMeta(const v8::FunctionCallbackInfo& info) +static void SyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IBaseObject); - - if (info.Length() == 2) - { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - ent->SetSyncedMetaData(key, value); - } - else if (info.Length() == 1 && info[0]->IsObject()) - { - auto dict = V8Helpers::CppValue>(info[0].As()); - std::unordered_map values; - - if (dict.has_value()) - { - for (auto& [key, value] : dict.value()) - values[key] = V8Helpers::V8ToMValue(value); - } - - ent->SetMultipleSyncedMetaData(values); - } + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetSyncedMetaData(ctx.GetProperty(), value); } -static void DeleteSyncedMeta(const v8::FunctionCallbackInfo& info) +static void SyncedMetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IBaseObject); + if(!obj->HasSyncedMetaData(ctx.GetProperty())) + { + ctx.Return(V8Helpers::JSValue(false)); + return; + } - ent->DeleteSyncedMetaData(key); + obj->DeleteSyncedMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } #endif // ALT_SERVER_API @@ -236,19 +139,12 @@ extern V8Class v8BaseObject("BaseObject", V8Helpers::SetAccessor(isolate, tpl, "remoteID"); V8Helpers::SetStaticMethod(isolate, tpl, "getByRemoteID", StaticGetByRemoteId); #endif // ALT_CLIENT_API - V8Helpers::SetMethod(isolate, tpl, "hasSyncedMeta", HasSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getSyncedMeta", GetSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getSyncedMetaKeys", GetSyncedMetaDataKeys); - - V8Helpers::SetMethod(isolate, tpl, "hasMeta", HasMeta); - V8Helpers::SetMethod(isolate, tpl, "getMeta", GetMeta); - V8Helpers::SetMethod(isolate, tpl, "setMeta", SetMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteMeta", DeleteMeta); - V8Helpers::SetMethod(isolate, tpl, "getMetaDataKeys", GetMetaDataKeys); + V8Helpers::SetDynamicProperty(isolate, tpl, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); + V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, nullptr, nullptr, SyncedMetaEnumerator); + V8Helpers::SetMethod(isolate, tpl, "destroy", Destroy); #ifdef ALT_SERVER_API - V8Helpers::SetMethod(isolate, tpl, "setSyncedMeta", SetSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteSyncedMeta", DeleteSyncedMeta); + V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, SyncedMetaSetter, SyncedMetaDeleter, SyncedMetaEnumerator); #endif // ALT_SERVER_API }); diff --git a/shared/bindings/BindingsMain.cpp b/shared/bindings/BindingsMain.cpp index d7e529a3..701260e8 100644 --- a/shared/bindings/BindingsMain.cpp +++ b/shared/bindings/BindingsMain.cpp @@ -103,95 +103,50 @@ static void EmitRaw(const v8::FunctionCallbackInfo& info) alt::ICore::Instance().TriggerLocalEventOnMain(name, args); } -static void HasMeta(const v8::FunctionCallbackInfo& info) +static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN_MIN(1); - V8_ARG_TO_STRING(1, key); - - V8_RETURN(alt::ICore::Instance().HasMetaData(key)); + auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetMeta(const v8::FunctionCallbackInfo& info) +static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN_MIN(1); - V8_ARG_TO_STRING(1, key); - - V8_RETURN_MVALUE(alt::ICore::Instance().GetMetaData(key)); + alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); + alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); } -static void GetMetaKeys(const v8::FunctionCallbackInfo& info) +static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - const std::vector list = alt::ICore::Instance().GetMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) + if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); + ctx.Return(V8Helpers::JSValue(false)); + return; } - V8_RETURN(arr); -} - -static void SetMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN_MIN(2); - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - alt::ICore::Instance().SetMetaData(key, value); -} - -static void DeleteMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN_MIN(1); - V8_ARG_TO_STRING(1, key); - - alt::ICore::Instance().DeleteMetaData(key); + alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } -static void HasSyncedMeta(const v8::FunctionCallbackInfo& info) +static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN_MIN(1); - V8_ARG_TO_STRING(1, key); - - V8_RETURN(alt::ICore::Instance().HasSyncedMetaData(key)); + auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + ctx.Return(keys); } -static void GetSyncedMeta(const v8::FunctionCallbackInfo& info) +static void SyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN_MIN(1); - V8_ARG_TO_STRING(1, key); - - V8_RETURN_MVALUE(alt::ICore::Instance().GetSyncedMetaData(key)); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetSyncedMetaKeys(const v8::FunctionCallbackInfo& info) +static void SyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - const std::vector list = alt::ICore::Instance().GetSyncedMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) - { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); - } - - V8_RETURN(arr); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetSyncedMetaDataKeys()); + ctx.Return(value); } static void Log(const v8::FunctionCallbackInfo& info) @@ -532,15 +487,8 @@ extern V8Module V8Helpers::RegisterFunc(exports, "getEventListeners", &GetEventListeners); V8Helpers::RegisterFunc(exports, "getRemoteEventListeners", &GetRemoteEventListeners); - V8Helpers::RegisterFunc(exports, "hasMeta", &HasMeta); - V8Helpers::RegisterFunc(exports, "getMeta", &GetMeta); - V8Helpers::RegisterFunc(exports, "setMeta", &SetMeta); - V8Helpers::RegisterFunc(exports, "deleteMeta", &DeleteMeta); - V8Helpers::RegisterFunc(exports, "getMetaKeys", &GetMetaKeys); - - V8Helpers::RegisterFunc(exports, "hasSyncedMeta", &HasSyncedMeta); - V8Helpers::RegisterFunc(exports, "getSyncedMeta", &GetSyncedMeta); - V8Helpers::RegisterFunc(exports, "getSyncedMetaKeys", &GetSyncedMetaKeys); + V8Helpers::RegisterDynamicProperty(isolate, exports, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); + V8Helpers::RegisterDynamicProperty(isolate, exports, "syncedMeta", SyncedMetaGetter, nullptr, nullptr, SyncedMetaEnumerator); V8Helpers::RegisterFunc(exports, "nextTick", &NextTick); V8Helpers::RegisterFunc(exports, "everyTick", &EveryTick); diff --git a/shared/bindings/Entity.cpp b/shared/bindings/Entity.cpp index e5516739..97244a7c 100644 --- a/shared/bindings/Entity.cpp +++ b/shared/bindings/Entity.cpp @@ -177,14 +177,6 @@ static void RotationSetter(v8::Local, v8::Local val, cons _this->SetRotation(vector); } -static void IsSpawnedGetter(v8::Local, const v8::PropertyCallbackInfo& info) -{ - V8_GET_ISOLATE(); - V8_GET_THIS_BASE_OBJECT(entity, alt::IEntity); - - V8_RETURN_BOOLEAN(entity->GetScriptID() != 0); -} - static void GetSyncInfo(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); @@ -296,7 +288,6 @@ extern V8Class v8Entity("Entity", V8Helpers::SetAccessor(isolate, tpl, "scriptID"); V8Helpers::SetAccessor(isolate, tpl, "visible"); - V8Helpers::SetAccessor(isolate, tpl, "isSpawned", &IsSpawnedGetter); V8Helpers::SetMethod(isolate, tpl, "getSyncInfo", &GetSyncInfo); #endif // ALT_CLIENT_API }); diff --git a/shared/bindings/VirtualEntity.cpp b/shared/bindings/VirtualEntity.cpp index ce9b0747..e4f3a5d6 100644 --- a/shared/bindings/VirtualEntity.cpp +++ b/shared/bindings/VirtualEntity.cpp @@ -42,155 +42,75 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllVirtualEntities()); } -static void HasMeta(const v8::FunctionCallbackInfo& info) +static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - V8_RETURN_BOOLEAN(ent->HasMetaData(key)); + auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetMeta(const v8::FunctionCallbackInfo& info) +static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - V8_RETURN_MVALUE(ent->GetMetaData(key)); + alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); + alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); } -static void GetMetaKeys(const v8::FunctionCallbackInfo& info) +static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - const std::vector list = ent->GetMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) + if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); + ctx.Return(V8Helpers::JSValue(false)); + return; } - V8_RETURN(arr); + alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } -static void SetMeta(const v8::FunctionCallbackInfo& info) +static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - if (info.Length() == 2) - { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - ent->SetMetaData(key, value); - } - else if (info.Length() == 1 && info[0]->IsObject()) - { - - } + auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + ctx.Return(keys); } -static void DeleteMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - ent->DeleteMetaData(key); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetStreamSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void HasStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - V8_RETURN_BOOLEAN(ent->HasStreamSyncedMetaData(key)); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetStreamSyncedMetaDataKeys()); + ctx.Return(value); } -static void GetStreamSyncedMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - V8_RETURN_MVALUE(ent->GetStreamSyncedMetaData(key)); -} +#ifdef ALT_SERVER_API -static void GetStreamSyncedMetaDataKeys(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - const std::vector list = ent->GetStreamSyncedMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) - { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); - } - - V8_RETURN(arr); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetStreamSyncedMetaData(ctx.GetProperty(), value); } -#ifdef ALT_SERVER_API - -static void SetStreamSyncedMeta(const v8::FunctionCallbackInfo& info) +static void StreamSyncedMetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - - if (info.Length() == 2) - { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); - ent->SetStreamSyncedMetaData(key, value); - } - else if (info.Length() == 1 && info[0]->IsObject()) + if(!obj->HasStreamSyncedMetaData(ctx.GetProperty())) { - auto dict = V8Helpers::CppValue>(info[0].As()); - std::unordered_map values; - - if (dict.has_value()) - { - for (auto& [key, value] : dict.value()) - values[key] = V8Helpers::V8ToMValue(value); - } - - ent->SetMultipleStreamSyncedMetaData(values); + ctx.Return(V8Helpers::JSValue(false)); + return; } -} - -static void DeleteStreamSyncedMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntity); - ent->DeleteStreamSyncedMetaData(key); + obj->DeleteStreamSyncedMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } #endif // ALT_SERVER_API @@ -222,19 +142,14 @@ extern V8Class v8VirtualEntity("VirtualEntity", V8Helpers::SetAccessor(isolate, tpl, "streamingDistance"); V8Helpers::SetAccessor(isolate, tpl, "visible"); - V8Helpers::SetMethod(isolate, tpl, "hasMeta", HasMeta); - V8Helpers::SetMethod(isolate, tpl, "getMeta", GetMeta); - V8Helpers::SetMethod(isolate, tpl, "getMetaKeys", GetMetaKeys); - V8Helpers::SetMethod(isolate, tpl, "setMeta", SetMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteMeta", DeleteMeta); + V8Helpers::SetDynamicProperty(isolate, tpl, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); - V8Helpers::SetMethod(isolate, tpl, "hasStreamSyncedMeta", HasStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getStreamSyncedMeta", GetStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "getStreamSyncedMetaKeys", GetStreamSyncedMetaDataKeys); + // Client will share only get and enumerate + V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, nullptr, nullptr, StreamSyncedMetaEnumerator); #ifdef ALT_SERVER_API - V8Helpers::SetMethod(isolate, tpl, "setStreamSyncedMeta", SetStreamSyncedMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteStreamSyncedMeta", DeleteStreamSyncedMeta); + // Server has also setter and deleter + V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, StreamSyncedMetaSetter, StreamSyncedMetaDeleter, StreamSyncedMetaEnumerator); #endif // ALT_SERVER_API #ifdef ALT_CLIENT_API diff --git a/shared/bindings/VirtualEntityGroup.cpp b/shared/bindings/VirtualEntityGroup.cpp index 310ce123..2c68ada6 100644 --- a/shared/bindings/VirtualEntityGroup.cpp +++ b/shared/bindings/VirtualEntityGroup.cpp @@ -24,83 +24,34 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllVirtualEntityGroups()); } -static void HasMeta(const v8::FunctionCallbackInfo& info) +static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntityGroup); - - V8_RETURN_BOOLEAN(ent->HasMetaData(key)); -} - -static void GetMeta(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntityGroup); - - V8_RETURN_MVALUE(ent->GetMetaData(key)); + auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetMetaData(ctx.GetProperty())); + ctx.Return(value); } -static void GetMetaKeys(const v8::FunctionCallbackInfo& info) +static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntityGroup); - - const std::vector list = ent->GetMetaDataKeys(); - size_t size = list.size(); - v8::Local arr = v8::Array::New(isolate, size); - for(size_t i = 0; i < size; i++) - { - arr->Set(ctx, i, V8Helpers::JSValue(list[i])); - } - - V8_RETURN(arr); + alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); + alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); } -static void SetMeta(const v8::FunctionCallbackInfo& info) +static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntityGroup); - - if (info.Length() == 2) + if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) { - V8_ARG_TO_STRING(1, key); - V8_ARG_TO_MVALUE(2, value); - - ent->SetMetaData(key, value); + ctx.Return(V8Helpers::JSValue(false)); + return; } - else if (info.Length() == 1 && info[0]->IsObject()) - { - auto dict = V8Helpers::CppValue>(info[0].As()); - std::unordered_map values; - if (dict.has_value()) - { - for (auto& [key, value] : dict.value()) - values[key] = V8Helpers::V8ToMValue(value); - } - - ent->SetMultipleMetaData(values); - } + alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); } -static void DeleteMeta(const v8::FunctionCallbackInfo& info) +static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - V8_GET_ISOLATE_CONTEXT(); - - V8_CHECK_ARGS_LEN(1); - V8_ARG_TO_STRING(1, key); - - V8_GET_THIS_BASE_OBJECT(ent, alt::IVirtualEntityGroup); - - ent->DeleteMetaData(key); + auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + ctx.Return(keys); } extern V8Class v8BaseObject; @@ -113,11 +64,7 @@ extern V8Class v8VirtualEntityGroup("VirtualEntityGroup", V8Helpers::SetStaticAccessor(isolate, tpl, "all", AllGetter); - V8Helpers::SetMethod(isolate, tpl, "hasMeta", HasMeta); - V8Helpers::SetMethod(isolate, tpl, "getMeta", GetMeta); - V8Helpers::SetMethod(isolate, tpl, "getMetaKeys", GetMetaKeys); - V8Helpers::SetMethod(isolate, tpl, "setMeta", SetMeta); - V8Helpers::SetMethod(isolate, tpl, "deleteMeta", DeleteMeta); + V8Helpers::SetDynamicProperty(isolate, tpl, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); V8Helpers::SetAccessor(isolate, tpl, "maxEntitiesInStream"); }); diff --git a/shared/bindings/js/classes/sharedBaseObject.js b/shared/bindings/js/classes/sharedBaseObject.js new file mode 100644 index 00000000..4c343d93 --- /dev/null +++ b/shared/bindings/js/classes/sharedBaseObject.js @@ -0,0 +1,40 @@ +const { isObject } = requireBinding("shared/utils.js"); + +export class SharedBaseObject { + hasMeta(key) { + return this.meta[key] !== undefined; + } + + getMeta(key) { + return this.meta[key]; + } + + setMeta(key, value) { + if (isObject(key)) { + this.setMultipleMetaData(key); + return; + } + + this.meta[key] = value; + } + + deleteMeta(key) { + delete this.meta[key]; + } + + getMetaDataKeys() { + return Object.keys(this.meta); + } + + getSyncedMeta(key) { + return this.syncedMeta[key]; + } + + hasSyncedMeta(key) { + return this.syncedMeta[key] !== undefined; + } + + getSyncedMetaKeys() { + return Object.keys(this.syncedMeta); + } +} diff --git a/shared/helpers/Bindings.cpp b/shared/helpers/Bindings.cpp index 1c96a82f..ca9c9f95 100644 --- a/shared/helpers/Bindings.cpp +++ b/shared/helpers/Bindings.cpp @@ -1,4 +1,6 @@ #include "Bindings.h" + +#include "BindHelpers.h" #include "Serialization.h" #include "V8ResourceImpl.h" @@ -41,6 +43,23 @@ void V8Helpers::RegisterBindingExport(v8::Local exports, const std:: exports->Set(ctx, V8Helpers::JSValue(_name), data); } +void V8Helpers::RegisterDynamicProperty(v8::Isolate* isolate, v8::Local exports, + const std::string& name, + js::internal::DynamicPropertyGetter getter, + js::internal::DynamicPropertySetter setter, + js::internal::DynamicPropertyDeleter deleter, + js::internal::DynamicPropertyEnumerator enumerator) +{ + js::DynamicPropertyData* data = new js::DynamicPropertyData(getter, setter, deleter, enumerator); + if(getter) data->getter = getter; + if(setter) data->setter = setter; + if(deleter) data->deleter = deleter; + if(enumerator) data->enumerator = enumerator; + + exports->SetLazyDataProperty(isolate->GetCurrentContext(), + V8Helpers::JSValue(name), DynamicPropertyLazyHandler, v8::External::New(isolate, data), (v8::PropertyAttribute)(v8::ReadOnly | v8::DontEnum)); +} + void V8Helpers::FunctionCallback(const v8::FunctionCallbackInfo& info) { auto fn = static_cast(info.Data().As()->Value()); diff --git a/shared/helpers/Bindings.h b/shared/helpers/Bindings.h index fc90243e..bf801925 100644 --- a/shared/helpers/Bindings.h +++ b/shared/helpers/Bindings.h @@ -4,6 +4,7 @@ #include "cpp-sdk/ICore.h" #include "V8FastFunction.h" #include "BindingHandler.h" +#include "JSTemplate.h" #include "magic_enum/magic_enum.hpp" @@ -26,6 +27,12 @@ namespace V8Helpers exports->Set(ctx, v8::String::NewFromUtf8(isolate, name.c_str()).ToLocalChecked(), enumObject).Check(); } void RegisterBindingExport(v8::Local exports, const std::string& _name, js::BindingExport _export); + void RegisterDynamicProperty(v8::Isolate* isolate, v8::Local exports, + const std::string& _name, + js::internal::DynamicPropertyGetter getter, + js::internal::DynamicPropertySetter setter = nullptr, + js::internal::DynamicPropertyDeleter deleter = nullptr, + js::internal::DynamicPropertyEnumerator enumerator = nullptr); void FunctionCallback(const v8::FunctionCallbackInfo& info); diff --git a/shared/helpers/JSTemplate.h b/shared/helpers/JSTemplate.h index 568339f6..bc5c6b88 100644 --- a/shared/helpers/JSTemplate.h +++ b/shared/helpers/JSTemplate.h @@ -3,8 +3,6 @@ #include "v8.h" #include "cpp-sdk/SDK.h" -#include "V8Helpers.h" - namespace js { template @@ -45,7 +43,8 @@ namespace js { if(GetParent() == nullptr) { - V8Helpers::Throw(this->GetIsolate(), "Invalid parent base object"); + const char* data = "Invalid parent base object"; + this->GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(this->GetIsolate(), data, v8::NewStringType::kNormal, sizeof(data)).ToLocalChecked())); return false; } return true; From 5317700710d6e7a2751d3ac01593538147a50120 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Tue, 28 Jan 2025 15:17:30 +0100 Subject: [PATCH 08/22] ALTV-659: Added js helpers --- shared/bindings/SharedCppBindings.cpp | 44 +++++++++++++++++++++++++++ shared/bindings/js/utils.js | 3 ++ shared/helpers/Bindings.h | 36 ++++++++++++++++++++++ shared/helpers/JS.h | 39 +++++++++++++++++++----- 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/shared/bindings/SharedCppBindings.cpp b/shared/bindings/SharedCppBindings.cpp index 95929830..9e23fccc 100644 --- a/shared/bindings/SharedCppBindings.cpp +++ b/shared/bindings/SharedCppBindings.cpp @@ -2,6 +2,43 @@ #include "../V8ResourceImpl.h" #include "../V8Module.h" +static void ResourceNameGetter(js::internal::DynamicPropertyGetterContext& ctx) +{ + v8::Isolate* isolate = ctx.GetIsolate(); + v8::Local context = isolate->GetEnteredOrMicrotaskContext(); + V8ResourceImpl* resource = V8ResourceImpl::Get(context); + + const char* resourceName = resource->GetResource()->GetName().c_str(); + v8::Local value = v8::String::NewFromUtf8(isolate, resourceName).ToLocalChecked(); + ctx.Return(value); +} + +static void GetCurrentSourceLocation(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + V8_CHECK_ARGS_LEN(1); + + V8_ARG_TO_INT(1, framesToSkip); + + v8::Local obj = v8::Object::New(isolate); + V8Helpers::SourceLocation location = V8Helpers::SourceLocation::GetCurrent(isolate, resource); + obj->Set(ctx, V8Helpers::JSValue("fileName"), V8Helpers::JSValue(location.GetFileName())).Check(); + obj->Set(ctx, V8Helpers::JSValue("lineNumber"), V8Helpers::JSValue(location.GetLineNumber())).Check(); + + V8_RETURN(obj); +} + +static void ToggleEvent(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT(); + V8_CHECK_ARGS_LEN(2); + + V8_ARG_TO_INT(1, type); + V8_ARG_TO_BOOLEAN(2, state); + + alt::ICore::Instance().ToggleEvent((alt::CEvent::Type)type, state); +} + static void RegisterExport(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -42,8 +79,15 @@ extern V8Module {}, [](v8::Local ctx, v8::Local exports) { + v8::Isolate* isolate = ctx->GetIsolate(); + V8Helpers::RegisterFunc(exports, "registerExport", &RegisterExport); V8Helpers::RegisterFunc(exports, "getBuiltinModule", &GetBuiltinModule); + V8Helpers::RegisterDynamicProperty(isolate, exports, "resourceName", ResourceNameGetter, nullptr, nullptr, nullptr); + + V8Helpers::RegisterFunc(exports, "toggleEvent", &ToggleEvent); + V8Helpers::RegisterFunc(exports, "getCurrentSourceLocation", &GetCurrentSourceLocation); + V8Helpers::RegisterEnum(exports, "BindingExport"); }); \ No newline at end of file diff --git a/shared/bindings/js/utils.js b/shared/bindings/js/utils.js index 541e345d..de63609a 100644 --- a/shared/bindings/js/utils.js +++ b/shared/bindings/js/utils.js @@ -187,6 +187,9 @@ export function assertRGBA(val, message = "Expected RGBA") { export function assertIsType(value, type, message) { assert(typeof value === type, message); } +export function isAsyncFunction(val) { + return typeof val == "function" && val.constructor.name === "AsyncFunction"; +} export function assertVector3(val, message = "Expected Vector3") { return alt.Utils.assert( val && typeof val.x === "number" && typeof val.y === "number" && typeof val.z === "number", diff --git a/shared/helpers/Bindings.h b/shared/helpers/Bindings.h index bf801925..34ae422e 100644 --- a/shared/helpers/Bindings.h +++ b/shared/helpers/Bindings.h @@ -10,6 +10,9 @@ namespace V8Helpers { + using LazyPropertyContext = v8::PropertyCallbackInfo; + using LazyPropertyCallback = void (*)(LazyPropertyContext&); + void RegisterFunc(v8::Local exports, const std::string& _name, v8::FunctionCallback cb, void* data = nullptr); template void RegisterEnum(v8::Local exports, const std::string& name) @@ -36,6 +39,39 @@ namespace V8Helpers void FunctionCallback(const v8::FunctionCallbackInfo& info); + template + void EnumObjectGetter(LazyPropertyContext& info) + { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local ctx = isolate->GetCurrentContext(); + v8::Local obj = v8::Object::New(isolate); + auto values = magic_enum::template enum_entries(); + for(auto& [value, key] : values) + { + obj->Set(ctx, + V8Helpers::JSValue(key.data()), + V8Helpers::JSValue((std::string_view)std::to_string((int)value)) + ).Check(); + } + info.GetReturnValue().Set(obj); + } + inline void LazyPropertyHandler(v8::Local, const v8::PropertyCallbackInfo& info) + { + LazyPropertyContext ctx{ info }; + auto callback = reinterpret_cast(info.Data().As()->Value()); + callback(ctx); + } + inline void StaticLazyProperty(const v8::Local& tpl, const std::string& name, LazyPropertyCallback callback) + { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + tpl->SetLazyDataProperty(V8Helpers::JSValue(name), LazyPropertyHandler, v8::External::New(isolate, (void*)callback)); + } + template + void StaticEnum(const v8::Local& tpl, const std::string& name) + { + StaticLazyProperty(tpl, name, EnumObjectGetter); + } + void SetAccessor(v8::Local tpl, v8::Isolate* isolate, const char* name, v8::AccessorGetterCallback getter, v8::AccessorSetterCallback setter = nullptr); void DefineOwnProperty(v8::Isolate* isolate, diff --git a/shared/helpers/JS.h b/shared/helpers/JS.h index 8d171748..b02fd27c 100644 --- a/shared/helpers/JS.h +++ b/shared/helpers/JS.h @@ -61,18 +61,41 @@ namespace js } // Falls back to default value if the value is not found or the type doesn't match - inline std::string GetV8ObjectValue(v8::Local context, v8::Local object, const std::string& key, const std::string& defaultValue = "") + template + inline T GetV8ObjectValue(v8::Local context, v8::Local object, const std::string& key, T defaultValue = T()) { v8::Isolate* isolate = context->GetIsolate(); v8::Local v8Key = v8::String::NewFromUtf8(isolate, key.c_str(), v8::NewStringType::kNormal).ToLocalChecked(); - v8::Local value = object->Get(isolate->GetCurrentContext(), v8Key).ToLocalChecked(); - // Check if the value is a string - if (!value->IsString()) { - return defaultValue; + v8::Local value; + + // Attempt to retrieve the value + if (!object->Get(isolate->GetCurrentContext(), v8Key).ToLocal(&value)) + return defaultValue; + + // Handle specific types + if constexpr (std::is_same_v) { + if (value->IsString()) { + v8::String::Utf8Value utf8Value(isolate, value); + return std::string(*utf8Value, utf8Value.length()); + } + } else if constexpr (std::is_integral_v) { + if (value->IsInt32() || value->IsUint32()) { + return static_cast(value->Int32Value(isolate->GetCurrentContext()).ToChecked()); + } + } else if constexpr (std::is_floating_point_v) { + if (value->IsNumber()) { + return static_cast(value->NumberValue(isolate->GetCurrentContext()).ToChecked()); + } + } else if constexpr (std::is_same_v) { + if (value->IsString()) { + v8::String::Utf8Value utf8Value(isolate, value); + if (utf8Value.length() == 1) { + return static_cast(**utf8Value); + } + } } - // Convert the V8 string to a C++ string - v8::String::Utf8Value utf8Value(isolate, value); - return std::string(*utf8Value, utf8Value.length()); + // Fallback if type doesn't match + return defaultValue; } } \ No newline at end of file From dad8c6fc9837ff3005626479d0a430721e55f464 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Tue, 28 Jan 2025 15:19:56 +0100 Subject: [PATCH 09/22] ALTV-659: Added enums bindings --- shared/bindings/EnumBindings.cpp | 29 +++++++++++++++++++++ shared/bindings/js/enums/customEventType.js | 3 +++ shared/bindings/js/enums/timerType.js | 3 +++ shared/bindings/js/helpers/enums.js | 27 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 shared/bindings/EnumBindings.cpp create mode 100644 shared/bindings/js/enums/customEventType.js create mode 100644 shared/bindings/js/enums/timerType.js create mode 100644 shared/bindings/js/helpers/enums.js diff --git a/shared/bindings/EnumBindings.cpp b/shared/bindings/EnumBindings.cpp new file mode 100644 index 00000000..d3847052 --- /dev/null +++ b/shared/bindings/EnumBindings.cpp @@ -0,0 +1,29 @@ +#include "../V8Helpers.h" +#include "../V8Class.h" + +#include "cpp-sdk/SDK.h" +#include "cpp-sdk/events/CKeyboardEvent.h" + +extern V8Class + v8AltEnums("Enums", + [](v8::Local tpl) + { + V8Helpers::StaticEnum(tpl, "BaseObjectType"); + V8Helpers::StaticEnum(tpl, "ExplosionType"); + V8Helpers::StaticEnum(tpl, "Permission"); + V8Helpers::StaticEnum(tpl, "BlipType"); + V8Helpers::StaticEnum(tpl, "ColShapeType"); + V8Helpers::StaticEnum(tpl, "ConnectDeniedReason"); + V8Helpers::StaticEnum(tpl, "VehicleModelType"); + V8Helpers::StaticEnum(tpl, "BodyPart"); + V8Helpers::StaticEnum(tpl, "EventType"); + V8Helpers::StaticEnum(tpl, "Benefit"); + V8Helpers::StaticEnum(tpl, "MetricType"); + V8Helpers::StaticEnum(tpl, "AmmoSpecialType"); + V8Helpers::StaticEnum(tpl, "KeyState"); + V8Helpers::StaticEnum(tpl, "VoiceConnectionState"); + V8Helpers::StaticEnum(tpl, "MarkerType"); + V8Helpers::StaticEnum(tpl, "CloudAuthResult"); + V8Helpers::StaticEnum(tpl, "Benefit"); + V8Helpers::StaticEnum(tpl, "TextLabelAlignment"); + }); \ No newline at end of file diff --git a/shared/bindings/js/enums/customEventType.js b/shared/bindings/js/enums/customEventType.js new file mode 100644 index 00000000..fcd6bdaa --- /dev/null +++ b/shared/bindings/js/enums/customEventType.js @@ -0,0 +1,3 @@ +const { createNumericEnum } = requireBinding("shared/helpers/enums.js"); + +alt.Enums.CustomEventType = createNumericEnum(["ERROR"]); \ No newline at end of file diff --git a/shared/bindings/js/enums/timerType.js b/shared/bindings/js/enums/timerType.js new file mode 100644 index 00000000..65a540a7 --- /dev/null +++ b/shared/bindings/js/enums/timerType.js @@ -0,0 +1,3 @@ +const { createNumericEnum } = requireBinding("shared/helpers/enums.js"); + +alt.Enums.TimerType = createNumericEnum(["TIMER", "INTERVAL", "TIMEOUT", "EVERY_TICK", "NEXT_TICK"]); \ No newline at end of file diff --git a/shared/bindings/js/helpers/enums.js b/shared/bindings/js/helpers/enums.js new file mode 100644 index 00000000..d969c7ac --- /dev/null +++ b/shared/bindings/js/helpers/enums.js @@ -0,0 +1,27 @@ +/** + * + * @param {string[]} values + */ +export function createNumericEnum(values) { + const temp = {}; + for (let i = 0; i < values.length; ++i) { + temp[values[i]] = i; + } + + return Object.freeze(temp); +} + +/** + * + * @param {Object} obj + */ +export function createReverseLookupObject(obj) { + const newObj = {}; + + for (const key in obj) { + newObj[obj[key]] = key; + newObj[key] = obj[key]; + } + + return Object.freeze(newObj); +} From fd44ea90e02a1ec3dd700ffcd9c42e23e1818afb Mon Sep 17 00:00:00 2001 From: Potapenko Date: Tue, 28 Jan 2025 15:20:19 +0100 Subject: [PATCH 10/22] ALTV-659: Move timers methods to js --- server/src/CNodeScriptRuntime.cpp | 5 + shared/V8ResourceImpl.cpp | 66 ++--- shared/V8ResourceImpl.h | 66 ++--- shared/V8Timer.h | 54 ---- shared/bindings/BindingsMain.cpp | 128 ++------- shared/bindings/js/helpers/events.js | 402 +++++++++++++++++++++++++++ shared/bindings/js/timers.js | 252 +++++++++++++++++ shared/helpers/BindingHandler.h | 15 + 8 files changed, 735 insertions(+), 253 deletions(-) delete mode 100644 shared/V8Timer.h create mode 100644 shared/bindings/js/helpers/events.js create mode 100644 shared/bindings/js/timers.js diff --git a/server/src/CNodeScriptRuntime.cpp b/server/src/CNodeScriptRuntime.cpp index 4793c480..c55996f1 100644 --- a/server/src/CNodeScriptRuntime.cpp +++ b/server/src/CNodeScriptRuntime.cpp @@ -76,6 +76,11 @@ void CNodeScriptRuntime::OnTick() void CNodeScriptRuntime::OnDispose() { + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope seal(isolate); + v8::Context::Scope scope(context.Get(isolate)); + V8Module::Cleanup(isolate); V8Class::UnloadAll(isolate); if(CProfiler::Instance().IsEnabled()) CProfiler::Instance().Dump("./"); diff --git a/shared/V8ResourceImpl.cpp b/shared/V8ResourceImpl.cpp index 3ea9ed58..28f9b5b2 100644 --- a/shared/V8ResourceImpl.cpp +++ b/shared/V8ResourceImpl.cpp @@ -35,20 +35,14 @@ bool V8ResourceImpl::Stop() } } - for(auto pair : timers) - { - delete pair.second; - } - timers.clear(); for(auto ent : entities) { delete ent.second; } entities.clear(); - oldTimers.clear(); resourceObjects.clear(); + nextTickCallbacks.clear(); - benchmarkTimers.clear(); localHandlers.clear(); remoteHandlers.clear(); @@ -69,37 +63,12 @@ bool V8ResourceImpl::Stop() void V8ResourceImpl::OnTick() { + v8::Local onTick = GetBindingHandler()->GetBindingExport(js::BindingExport::TICK); + onTick->Call(GetContext(), v8::Undefined(isolate), 0, nullptr); + for(auto& nextTickCb : nextTickCallbacks) nextTickCb(); nextTickCallbacks.clear(); - for(auto& id : oldTimers) - { - auto it = timers.find(id); - if(it == timers.end()) - continue; - - auto timer = it->second; - timers.erase(it); - delete timer; - } - - oldTimers.clear(); - - for(auto& p : timers) - { - if(std::find(oldTimers.begin(), oldTimers.end(), p.first) != oldTimers.end()) continue; - int64_t time = GetTime(); - if(!p.second->Update(time)) RemoveTimer(p.first); - - if(GetTime() - time > 50) - { - auto& location = p.second->GetLocation(); - - Log::Warning << "Timer at " << location.GetFuncName() << " (" << resource->GetName() << ":" << location.GetFileName() - << ":" << location.GetLineNumber() << ") was too long " << (GetTime() - time) << "ms" << Log::Endl; - } - } - for(auto it = localHandlers.begin(); it != localHandlers.end();) { if(it->second.removed) it = localHandlers.erase(it); @@ -632,10 +601,29 @@ void V8ResourceImpl::InvokeEventHandlers(const alt::CEvent* ev, const std::vecto void V8ResourceImpl::PrintHealth() { + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope seal(isolate); + v8::Context::Scope scope(context.Get(isolate)); + + v8::Local timersCountFunc = GetBindingHandler()->GetBindingExport(js::BindingExport::TIMERS_COUNT); + v8::Local timersData = timersCountFunc->Call(GetContext(), v8::Undefined(isolate), 0, nullptr).ToLocalChecked(); + + if (!timersData->IsObject()) + { + Log::Error << GetResource()->GetName() << ": Cannot convert function return value to object!" << Log::Endl; + return; + } + + v8::Local obj = timersData->ToObject(GetContext()).ToLocalChecked(); + + int totalCount = js::GetV8ObjectValue(GetContext(), obj, "totalCount"); + int benchmarkCount = js::GetV8ObjectValue(GetContext(), obj, "benchmarkCount"); + Log::Info << "Resource health: " << resource->GetName() << Log::Endl; Log::Info << " - Entities: " << entities.size() << Log::Endl; - Log::Info << " - Timers: " << timers.size() << Log::Endl; - Log::Info << " - Timer benchmarks: " << benchmarkTimers.size() << Log::Endl; + Log::Info << " - Timers: " << totalCount << Log::Endl; + Log::Info << " - Timer benchmarks: " << benchmarkCount << Log::Endl; #ifdef ALT_SERVER_API Log::Info << " - Vehicle passengers: " << vehiclePassengers.size() << Log::Endl; @@ -819,8 +807,8 @@ void V8ResourceImpl::InitializeBinding(js::Binding *binding) { Log::Error << "INTERNAL ERROR: Failed to evaluate binding module " + binding->GetName() << Log::Endl; v8::Local exceptionObj = module->GetException().As(); - Log::Error << js::GetV8ObjectValue(context.Get(isolate), exceptionObj, "message") << Log::Endl; - std::string stack = js::GetV8ObjectValue(context.Get(isolate), exceptionObj, "stack"); + Log::Error << js::GetV8ObjectValue(context.Get(isolate), exceptionObj, "message") << Log::Endl; + std::string stack = js::GetV8ObjectValue(context.Get(isolate), exceptionObj, "stack"); if(!stack.empty()) Log::Error << stack << Log::Endl; } } diff --git a/shared/V8ResourceImpl.h b/shared/V8ResourceImpl.h index 5aa35af7..543727e4 100644 --- a/shared/V8ResourceImpl.h +++ b/shared/V8ResourceImpl.h @@ -8,7 +8,6 @@ #include "cpp-sdk/objects/IBaseObject.h" #include "V8Entity.h" -#include "V8Timer.h" #include "V8Module.h" #include "IRuntimeEventHandler.h" @@ -222,37 +221,29 @@ class V8ResourceImpl : public alt::IResource::Impl return alt::ICore::Instance().CreateMValueFunction(impl); } - uint32_t CreateTimer(v8::Local context, v8::Local callback, uint32_t interval, bool once, V8Helpers::SourceLocation&& location) - { - uint32_t id = ++nextTimerId; - timers[id] = new V8Timer{ isolate, context, GetTime(), callback, interval, once, std::move(location) }; - - return id; - } - - void RemoveTimer(uint32_t id) + void TimerBenchmark() { - oldTimers.push_back(id); - } + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope seal(isolate); + v8::Context::Scope scope(context.Get(isolate)); - bool DoesTimerExist(uint32_t id) - { - return timers.count(id) != 0; - } + v8::Local timersCountFunc = GetBindingHandler()->GetBindingExport(js::BindingExport::TIMERS_COUNT); + v8::Local timersData = timersCountFunc->Call(GetContext(), v8::Undefined(isolate), 0, nullptr).ToLocalChecked(); - void TimerBenchmark() - { - size_t totalCount = 0, everyTickCount = 0, intervalCount = 0, timeoutCount = 0; - totalCount = timers.size(); - for(auto [id, timer] : timers) + if (!timersData->IsObject()) { - if(timer->GetInterval() == 0 && !timer->IsOnce()) everyTickCount += 1; - else if(timer->IsOnce()) - timeoutCount += 1; - else - intervalCount += 1; + Log::Error << GetResource()->GetName() << ": Cannot convert function return value to object!" << Log::Endl; + return; } + v8::Local obj = timersData->ToObject(GetContext()).ToLocalChecked(); + + int totalCount = js::GetV8ObjectValue(GetContext(), obj, "totalCount"); + int everyTickCount = js::GetV8ObjectValue(GetContext(), obj, "everyTickCount"); + int intervalCount = js::GetV8ObjectValue(GetContext(), obj, "intervalCount"); + int timeoutCount = js::GetV8ObjectValue(GetContext(), obj, "timeoutCount"); + Log::Info << GetResource()->GetName() << ": " << totalCount << " running timers (" << everyTickCount << " EveryTick, " << intervalCount << " Interval, " << timeoutCount << " Timeout" << ")" << Log::Endl; } @@ -292,23 +283,6 @@ class V8ResourceImpl : public alt::IResource::Impl v8::Local GetOrCreateResourceObject(alt::IResource* resource); void DeleteResourceObject(alt::IResource* resource); - bool HasBenchmarkTimer(const std::string& name) - { - return benchmarkTimers.count(name) != 0; - } - void CreateBenchmarkTimer(const std::string& name) - { - benchmarkTimers.insert({ name, std::chrono::high_resolution_clock::now() }); - } - void RemoveBenchmarkTimer(const std::string& name) - { - benchmarkTimers.erase(name); - } - std::chrono::high_resolution_clock::time_point GetBenchmarkTimerStart(const std::string& name) - { - return benchmarkTimers.at(name); - } - v8::Local GetLogFunction() { return logFunction.Get(isolate); @@ -374,18 +348,12 @@ class V8ResourceImpl : public alt::IResource::Impl V8Helpers::CPersistent context; std::unordered_map entities; - std::unordered_map timers; - // Key = Name, Value = Start time - std::unordered_map benchmarkTimers; std::unordered_multimap localHandlers; std::unordered_multimap remoteHandlers; std::vector localGenericHandlers; std::vector remoteGenericHandlers; - uint32_t nextTimerId = 0; - std::vector oldTimers; - bool playerPoolDirty = true; V8Helpers::CPersistent players; diff --git a/shared/V8Timer.h b/shared/V8Timer.h deleted file mode 100644 index 69d06715..00000000 --- a/shared/V8Timer.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "V8Helpers.h" -#include "V8ResourceImpl.h" - -class V8Timer -{ -public: - V8Timer(v8::Isolate* _isolate, v8::Local _context, int64_t curTime, v8::Local _callback, uint32_t _interval, bool _once, V8Helpers::SourceLocation&& _location) - : isolate(_isolate), context(_isolate, _context), lastRun(curTime), callback(_isolate, _callback), interval(_interval), once(_once), location(std::move(_location)) - { - // Log::Debug << "Create timer: " << curTime << " " << interval << Log::Endl; - } - - bool Update(int64_t curTime) - { - if(curTime - lastRun >= interval) - { - V8Helpers::TryCatch([&] { - std::vector> args; - v8::MaybeLocal result = V8Helpers::CallFunctionWithTimeout(callback.Get(isolate), context.Get(isolate), args); - return !result.IsEmpty(); - }); - - lastRun = curTime; - - return !once; - } - - return true; - } - - const V8Helpers::SourceLocation& GetLocation() const - { - return location; - } - int64_t GetInterval() - { - return interval; - } - bool IsOnce() - { - return once; - } - -private: - v8::Isolate* isolate; - V8Helpers::CPersistent context; - V8Helpers::CPersistent callback; - int64_t interval; - int64_t lastRun = 0; - bool once; - V8Helpers::SourceLocation location; -}; diff --git a/shared/bindings/BindingsMain.cpp b/shared/bindings/BindingsMain.cpp index 701260e8..f5d7cc06 100644 --- a/shared/bindings/BindingsMain.cpp +++ b/shared/bindings/BindingsMain.cpp @@ -207,103 +207,6 @@ static void LogDebug(const v8::FunctionCallbackInfo& info) logFunction->Call(ctx, v8::Undefined(isolate), argsArr.size(), argsArr.data()); } -static void Time(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN2(0, 1); - - std::string name = ""; - if(info.Length() != 0) - { - V8_ARG_TO_STRING(1, timerName); - name = timerName; - } - - V8_CHECK(!resource->HasBenchmarkTimer(name), "Benchmark timer already exists"); - resource->CreateBenchmarkTimer(name); -} - -static void TimeEnd(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_ARGS_LEN2(0, 1); - - std::string name = ""; - if(info.Length() != 0) - { - V8_ARG_TO_STRING(1, timerName); - name = timerName; - } - - V8_CHECK(resource->HasBenchmarkTimer(name), "Benchmark timer not found"); - - std::chrono::high_resolution_clock::time_point start = resource->GetBenchmarkTimerStart(name); - Log::Info << "Timer " << name << ": " << (float)(std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count() / 1000.f) << "ms" - << Log::Endl; - - resource->RemoveBenchmarkTimer(name); -} - -static void SetTimeout(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN(2); - - V8_ARG_TO_FUNCTION(1, callback); - V8_ARG_TO_INT(2, time); - - V8_RETURN_INT(resource->CreateTimer(ctx, callback, time, true, V8Helpers::SourceLocation::GetCurrent(isolate, resource))); -} - -static void SetInterval(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN(2); - - V8_ARG_TO_FUNCTION(1, callback); - V8_ARG_TO_INT(2, time); - - V8_RETURN_INT(resource->CreateTimer(ctx, callback, time, false, V8Helpers::SourceLocation::GetCurrent(isolate, resource))); -} - -static void NextTick(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN(1); - - V8_ARG_TO_FUNCTION(1, callback); - - V8_RETURN_INT(resource->CreateTimer(ctx, callback, 0, true, V8Helpers::SourceLocation::GetCurrent(isolate, resource))); -} - -static void EveryTick(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN(1); - - V8_ARG_TO_FUNCTION(1, callback); - - V8_RETURN_INT(resource->CreateTimer(ctx, callback, 0, false, V8Helpers::SourceLocation::GetCurrent(isolate, resource))); -} - -static void ClearTimer(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_CHECK_ARGS_LEN(1); - - V8_CHECK(!info[0]->IsNullOrUndefined(), "Invalid timer id"); - V8_ARG_TO_INT(1, timer); - - V8_CHECK(resource->DoesTimerExist(timer), "Timer with that id does not exist"); - - resource->RemoveTimer(timer); -} - static void HasResource(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); @@ -460,12 +363,12 @@ static void AddEnumsToSharedModuleExports(v8::Isolate* isolate, v8::Local ctx, v8::Local exports) { v8::Isolate* isolate = ctx->GetIsolate(); @@ -475,8 +378,6 @@ extern V8Module V8Helpers::RegisterFunc(exports, "logWarning", &LogWarning); V8Helpers::RegisterFunc(exports, "logError", &LogError); V8Helpers::RegisterFunc(exports, "logDebug", &LogDebug); - V8Helpers::RegisterFunc(exports, "time", &Time); - V8Helpers::RegisterFunc(exports, "timeEnd", &TimeEnd); V8Helpers::RegisterFunc(exports, "on", &On); V8Helpers::RegisterFunc(exports, "once", &Once); @@ -490,16 +391,6 @@ extern V8Module V8Helpers::RegisterDynamicProperty(isolate, exports, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); V8Helpers::RegisterDynamicProperty(isolate, exports, "syncedMeta", SyncedMetaGetter, nullptr, nullptr, SyncedMetaEnumerator); - V8Helpers::RegisterFunc(exports, "nextTick", &NextTick); - V8Helpers::RegisterFunc(exports, "everyTick", &EveryTick); - V8Helpers::RegisterFunc(exports, "setTimeout", &SetTimeout); - V8Helpers::RegisterFunc(exports, "setInterval", &SetInterval); - V8Helpers::RegisterFunc(exports, "clearTimer", &ClearTimer); - V8Helpers::RegisterFunc(exports, "clearNextTick", &ClearTimer); - V8Helpers::RegisterFunc(exports, "clearEveryTick", &ClearTimer); - V8Helpers::RegisterFunc(exports, "clearTimeout", &ClearTimer); - V8Helpers::RegisterFunc(exports, "clearInterval", &ClearTimer); - V8Helpers::RegisterFunc(exports, "hasResource", &HasResource); V8Helpers::RegisterFunc(exports, "getAllResources", &GetAllResources); @@ -518,6 +409,21 @@ extern V8Module V8Helpers::RegisterBindingExport(exports, "Vector3", js::BindingExport::VECTOR3_CLASS); V8Helpers::RegisterBindingExport(exports, "RGBA", js::BindingExport::RGBA_CLASS); V8Helpers::RegisterBindingExport(exports, "Quaternion", js::BindingExport::QUATERNION_CLASS); + + // Timers + V8Helpers::RegisterBindingExport(exports, "everyTick", js::BindingExport::EVERY_TICK); + V8Helpers::RegisterBindingExport(exports, "nextTick", js::BindingExport::NEXT_TICK); + V8Helpers::RegisterBindingExport(exports, "setInterval", js::BindingExport::SET_INTERVAL); + V8Helpers::RegisterBindingExport(exports, "setTimeout", js::BindingExport::SET_TIMEOUT); + V8Helpers::RegisterBindingExport(exports, "time", js::BindingExport::TIME); + V8Helpers::RegisterBindingExport(exports, "timeEnd", js::BindingExport::TIME_END); + + // Timers for clear + V8Helpers::RegisterBindingExport(exports, "clearEveryTick", js::BindingExport::CLEAR_EVERY_TICK); + V8Helpers::RegisterBindingExport(exports, "clearInterval", js::BindingExport::CLEAR_INTERVAL); + V8Helpers::RegisterBindingExport(exports, "clearNextTick", js::BindingExport::CLEAR_NEXT_TICK); + V8Helpers::RegisterBindingExport(exports, "clearTimeout", js::BindingExport::CLEAR_TIMEOUT); + V8Helpers::RegisterBindingExport(exports, "clearTimer", js::BindingExport::CLEAR_TIMER); } V8_OBJECT_SET_STRING(exports, "version", alt::ICore::Instance().GetVersion()); diff --git a/shared/bindings/js/helpers/events.js b/shared/bindings/js/helpers/events.js new file mode 100644 index 00000000..4f10d725 --- /dev/null +++ b/shared/bindings/js/helpers/events.js @@ -0,0 +1,402 @@ +/** @type {typeof import("./utils.js")} */ +const { assertIsType, isAsyncFunction } = requireBinding("shared/utils.js"); + +export class Event { + /** @type {Map} */ + static #handlers = new Map(); + /** @type {Map} */ + static #customHandlers = new Map(); + /** @type {Set} */ + static #genericHandlers = new Set(); + + /** @type {Map} */ + static #localScriptEventHandlers = new Map(); + /** @type {Map} */ + static #remoteScriptEventHandlers = new Map(); + + /** Warning threshold in ms */ + static #warningThreshold = 100; + static setWarningThreshold(threshold) { + assertIsType(threshold, "number", "Expected a number as first argument"); + + Event.#warningThreshold = threshold; + } + + static #sourceLocationFrameSkipCount = 0; + static setSourceLocationFrameSkipCount(count) { + assertIsType(count, "number", "Expected a number as first argument"); + + Event.#sourceLocationFrameSkipCount = count; + } + + /** + * Gets the name of the event type enum value. + * @param {number} type + * @param {boolean} custom + */ + static getEventName(type, custom) { + const enumObj = custom ? alt.Enums.CustomEventType : alt.Enums.EventType; + return Object.keys(enumObj).find((key) => enumObj[key] === type); + } + + /** + * @param {string} name + * @param {number} type + * @param {boolean} custom + * @param {boolean} once + * @param {Function} handler + */ + static #subscribe(name, type, custom, once, handler) { + assertIsType(handler, "function", `Handler for event '${name}' is not a function`); + + const location = cppBindings.getCurrentSourceLocation(Event.#sourceLocationFrameSkipCount); + const eventHandler = new EventHandler(type, handler, location, custom, once); + + const map = custom ? Event.#customHandlers : Event.#handlers; + if (!map.has(type)) map.set(type, [eventHandler]); + else map.get(type).push(eventHandler); + + if (!custom) cppBindings.toggleEvent(type, true); + + return eventHandler; + } + + /** + * @param {string} name + * @param {number} type + * @param {boolean} custom + * @param {Function} handler + */ + static unsubscribe(type, custom, handler) { + const map = custom ? Event.#customHandlers : Event.#handlers; + const handlers = map.get(type); + if (!handlers) return; + const idx = handlers.findIndex((value) => value.handler === handler); + if (idx === -1) return; + handlers.splice(idx, 1); + + if (!custom) cppBindings.toggleEvent(type, false); + } + + /** + * @param {{ eventName: string }} ctx + * @param {boolean} local + */ + static #handleScriptEvent(ctx, local) { + const name = ctx.eventName; + const handlers = local ? Event.#localScriptEventHandlers.get(name) : Event.#remoteScriptEventHandlers.get(name); + if (!handlers) { + return []; + } + + const isPlayerScriptEvent = alt.isServer && !local; + + const promises = []; + for (let eventHandler of handlers) { + const { handler, location, onlyOnce, eventName } = eventHandler; + + try { + const startTime = alt.getNetTime(); + + const isAsync = isAsyncFunction(handler); + if (isPlayerScriptEvent) { + if (isAsync) promises.push(handler(ctx.player, ...ctx.args)); + else handler(ctx.player, ...ctx.args); + } else { + if (isAsync) promises.push(handler(...ctx.args)); + else handler(...ctx.args); + } + + const duration = alt.getNetTime() - startTime; + if (duration > Event.#warningThreshold) { + alt.logWarning(`Event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for script event '${name}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); + } + + if (onlyOnce) eventHandler.destroy(); + } catch (e) { + alt.logError(`Exception caught while invoking script event '${name}' handler`); + alt.logError(e); + + Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location, stack: e.stack }, true); + } + } + + return promises; + } + + static getEventHandlers() { + const obj = {}; + for (const [key, value] of Event.#handlers) obj[key] = value.filter((val) => val.location.fileName !== ""); + for (const [key, value] of Event.#customHandlers) obj[key] = value.filter((val) => val.location.fileName !== ""); + return obj; + } + + /** + * @param {string} name + * @param {number} type + * @param {boolean} custom + * @param {boolean} custom + */ + static #getEventFunc(name, type, custom, once = false) { + const func = Event.#subscribe.bind(undefined, name, type, custom, once); + return func; + } + + /** + * @param {boolean} local + */ + static getScriptEventHandlers(local) { + const map = local ? Event.#localScriptEventHandlers : Event.#remoteScriptEventHandlers; + const obj = {}; + for (let [key, value] of map) obj[key] = value.filter((val) => val.location.fileName !== ""); + return obj; + } + + /** + * @param {boolean} local + * @param {boolean?} once + */ + static getScriptEventFunc(local, once = false) { + const func = Event.#subscribeScriptEvent.bind(undefined, local, once); + return func; + } + + /** + * @param {boolean} local + * @param {boolean} once + * @param {string} name + * @param {Function} handler + */ + static #subscribeScriptEvent(local, once, name, handler) { + assertIsType(name, "string", `Event name is not a string`); + assertIsType(handler, "function", `Handler for ${local ? "local" : "remote"} script event '${name}' is not a function`); + + const location = cppBindings.getCurrentSourceLocation(Event.#sourceLocationFrameSkipCount); + const eventHandler = new ScriptEventHandler(name, local, handler, location, once); + + const map = local ? Event.#localScriptEventHandlers : Event.#remoteScriptEventHandlers; + if (!map.has(name)) map.set(name, [eventHandler]); + else map.get(name).push(eventHandler); + + return eventHandler; + } + + static unsubscribeScriptEvent(local, name, handler) { + const map = local ? Event.#localScriptEventHandlers : Event.#remoteScriptEventHandlers; + const handlers = map.get(name); + if (!handlers) return; + const idx = handlers.findIndex((value) => value.handler === handler); + if (idx === -1) return; + handlers.splice(idx, 1); + } + + /** + * @param {number} eventType + * @param {Record} ctx + * @param {boolean} custom + */ + static #invokeGeneric(eventType, ctx, custom) { + const handlers = Event.#genericHandlers; + if (!handlers.size) return []; + + const genericCtx = Object.freeze({ + ...ctx, + eventType, + customEvent: custom + }); + + const promises = []; + for (let { handler, location } of handlers) { + try { + const startTime = alt.getNetTime(); + + if (isAsyncFunction(handler)) promises.push(handler(genericCtx)); + else handler(genericCtx); + + const duration = alt.getNetTime() - startTime; + if (duration > Event.#warningThreshold) { + alt.logWarning(`Generic event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for event '${Event.getEventName(eventType, custom)}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); + } + } catch (e) { + alt.logError(`Exception caught while invoking generic event handler`); + alt.logError(e); + + Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location, stack: e.stack }, true); + } + } + + return promises; + } + + /** + * @param {Function} handler + */ + static subscribeGeneric(handler) { + assertIsType(handler, "function", `Handler for generic event is not a function`); + + const location = cppBindings.getCurrentSourceLocation(Event.#sourceLocationFrameSkipCount); + const eventHandler = new GenericEventHandler(handler, location); + + Event.#genericHandlers.add(eventHandler); + + return eventHandler; + } + + /** + * @param {Function} handler + */ + static unsubscribeGeneric(handler) { + Event.#genericHandlers.forEach((value) => { + if (value.handler === handler) Event.#genericHandlers.delete(value); + }); + } + + /** + * @param {number} type Event type + * @param {string} name Event name (e.g. `PlayerConnect` is accessible via `alt.Events.onPlayerConnect`) + * @param {string} custom alt:V built-in event or a custom JS module event + */ + static register(type, name, custom = false) { + alt.Events[`on${name}`] = Event.#getEventFunc(name, type, custom); + alt.Events[`once${name}`] = Event.#getEventFunc(name, type, custom, true); + } + + /** + * @param {number} eventType + * @param {Record} ctx + * @param {boolean} custom + */ + static invoke(eventType, ctx, custom) { + let promises = Event.#invokeGeneric(eventType, ctx, custom); + + if (eventType === alt.Enums.EventType.CLIENT_SCRIPT_EVENT) promises = [...Event.#handleScriptEvent(ctx, alt.isClient), ...promises]; + else if (eventType === alt.Enums.EventType.SERVER_SCRIPT_EVENT) promises = [...Event.#handleScriptEvent(ctx, alt.isServer), ...promises]; + + const map = custom ? Event.#customHandlers : Event.#handlers; + const handlers = map.get(eventType); + + if (!handlers) { + return promises; + } + + for (const eventHandler of handlers) { + const { handler, location, onlyOnce } = eventHandler; + + try { + const startTime = alt.getNetTime(); + if (isAsyncFunction(handler)) promises.push(handler(ctx)); + else handler(ctx); + + const duration = alt.getNetTime() - startTime; + if (duration > Event.#warningThreshold) { + alt.logWarning(`Event handler in resource '${cppBindings.resourceName}' (${location.fileName}:${location.lineNumber}) for event '${Event.getEventName(eventType, custom)}' took ${duration}ms to execute (Threshold: ${Event.#warningThreshold}ms)`); + } + + if (onlyOnce) { + eventHandler.destroy(); + } + } catch (e) { + alt.logError(`Exception caught while invoking event handler`); + alt.logError(e); + + Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location, stack: e.stack }, true); + } + } + + return promises; + } +} + +class EventHandler { + #eventType; + #handler; + #location; + #custom; + #once; + #valid = true; + + constructor(eventType, handler, location, custom, once) { + this.#eventType = eventType; + this.#handler = handler; + this.#location = location; + this.#custom = custom; + this.#once = once; + } + + #unregister() { + Event.unsubscribe(this.#eventType, this.#custom, this.#handler); + } + + destroy() { + if (!this.#valid) return; + this.#unregister(); + this.#valid = false; + } + + get eventType() { + return this.#eventType; + } + get eventTypeName() { + return Event.getEventName(this.#eventType, this.#custom); + } + get handler() { + return this.#handler; + } + get location() { + return this.#location; + } + get onlyOnce() { + return this.#once; + } + get valid() { + return this.#valid; + } +} + +class ScriptEventHandler extends EventHandler { + #eventName; + #local; + + constructor(eventName, local, handler, location, once) { + super(ScriptEventHandler.#getEventType(local), handler, location, false, once); + this.#eventName = eventName; + this.#local = local; + } + + #unregister() { + Event.unsubscribeScriptEvent(this.#local, this.#eventName, this.handler); + } + + destroy() { + this.#unregister(); + } + + get eventName() { + return this.#eventName; + } + + get local() { + return this.#local; + } + + get remote() { + return !this.#local; + } + + static #getEventType(local) { + if (local && alt.isClient) return alt.Enums.EventType.CLIENT_SCRIPT_EVENT; + else if (!local && alt.isClient) return alt.Enums.EventType.SERVER_SCRIPT_EVENT; + else if (local && alt.isServer) return alt.Enums.EventType.SERVER_SCRIPT_EVENT; + else if (!local && alt.isServer) return alt.Enums.EventType.CLIENT_SCRIPT_EVENT; + } +} + +class GenericEventHandler extends EventHandler { + constructor(handler, location) { + super(alt.Enums.EventType.ALL, handler, location, false); + } + + #unregister() { + Event.unsubscribeGeneric(this.handler); + } +} \ No newline at end of file diff --git a/shared/bindings/js/timers.js b/shared/bindings/js/timers.js new file mode 100644 index 00000000..e178af3c --- /dev/null +++ b/shared/bindings/js/timers.js @@ -0,0 +1,252 @@ +const { assert, assertIsType } = requireBinding("shared/utils.js"); +const { Event } = requireBinding("shared/helpers/events.js"); + +const timers = new Map(); + +class Timer { + static #_warningThreshold = 100; + static set warningThreshold(threshold) { + assertIsType(threshold, "number", "Expected a number as first argument"); + + Timer.#_warningThreshold = threshold; + } + + static get warningThreshold() { + return Timer.#_warningThreshold; + } + + static #_sourceLocationFrameSkipCount = 0; + static set sourceLocationFrameSkipCount(count) { + assertIsType(count, "number", "Expected a number as first argument"); + + Timer.#_sourceLocationFrameSkipCount = count; + } + + static get sourceLocationFrameSkipCount() { + return Timer.#_sourceLocationFrameSkipCount; + } + + /** @type {number} */ + interval; + /** @type {Function} */ + callback; + /** @type {number} */ + lastTick; + /** @type {boolean} */ + once; + + /** @type {{ fileName: string, lineNumber: number }} */ + location; + + #_type = alt.Enums.TimerType.TIMER; + + /** @type {number} */ + #_id = 0; + + get type() { + return this.#_type; + } + + get id() { + return this.#_id; + } + + /** + * @type {number} + */ + static #timerIncrementer = 1; + + /** + * @param {number} id + */ + static getByID(id) { + return timers.get(id) || null; + } + + /** + * + * @param {number | Timer} idOrHandle + */ + static isValid(idOrHandle) { + if (!idOrHandle) return false; + + const id = idOrHandle instanceof Timer ? idOrHandle.id : idOrHandle; + return timers.has(id); + } + + constructor(type, callback, interval, once, args) { + assertIsType(type, "number", "Expected a number as first argument"); + assertIsType(callback, "function", "Expected a function as second argument"); + assertIsType(interval, "number", "Expected a number as third argument"); + assertIsType(once, "boolean", "Expected a boolean as fourth argument"); + + this.interval = interval; + this.callback = callback.bind(this, ...(Array.isArray(args) ? args : [])); + this.lastTick = alt.getNetTime(); + this.once = once; + this.#_type = type; + this.#_id = Timer.#timerIncrementer++; + this.location = cppBindings.getCurrentSourceLocation(Timer.#_sourceLocationFrameSkipCount); + timers.set(this.#_id, this); + } + + destroy() { + timers.delete(this.#_id); + } + + tick() { + const now = alt.getNetTime(); + if (this.interval === 0 || now - this.lastTick >= this.interval) { + try { + this.callback(); + } catch (e) { + alt.logError(`Exception caught while invoking timer callback`); + alt.logError(e); + + Event.invoke(alt.Enums.CustomEventType.ERROR, { error: e, location: this.location, stack: e.stack }, true); + } + this.lastTick = alt.getNetTime(); + + const duration = this.lastTick - now; + if (duration > Timer.#_warningThreshold) { + alt.logWarning(`Timer callback in resource '${cppBindings.resourceName}' (${this.location.fileName}:${this.location.lineNumber}) took ${duration}ms to execute (Threshold: ${Timer.#_warningThreshold}ms)`); + } + + if (this.once) this.destroy(); + } + } +} + +class Interval extends Timer { + constructor(callback, interval, ...args) { + super(alt.Enums.TimerType.INTERVAL, callback, interval, false, args); + } +} + +class Timeout extends Timer { + constructor(callback, timeout, ...args) { + super(alt.Enums.TimerType.TIMEOUT, callback, timeout, true, args); + } +} + +class EveryTick extends Timer { + constructor(callback, ...args) { + super(alt.Enums.TimerType.EVERY_TICK, callback, 0, false, args); + } +} + +class NextTick extends Timer { + constructor(callback, ...args) { + super(alt.Enums.TimerType.NEXT_TICK, callback, 0, true, args); + } +} + +const timeMap = new Map(); + +/** + * + * @param {string | undefined} name + */ +function time(name) { + const key = typeof name == "string" ? name : ""; + + if (timeMap.has(key)) { + throw new Error(`Benchmark timer ${key} already exists`); + } + + timeMap.set(key, alt.getNetTime()); +} + +/** + * + * @param {string | undefined} name + */ +function timeEnd(name) { + const key = typeof name == "string" ? name : ""; + + if (!timeMap.has(key)) { + throw new Error(`Benchmark timer ${key} not found`); + } + + const diff = alt.getNetTime() - timeMap.get(key); + timeMap.delete(key); + + alt.log(`Timer ${key}: ${diff}ms`); +} + +alt.Utils.Interval = Interval; +alt.Utils.Timeout = Timeout; +alt.Utils.EveryTick = EveryTick; +alt.Utils.NextTick = NextTick; + +const setInterval = (callback, interval, ...args) => { + const timer = new Interval(callback, interval, ...args); + return timer.id; +} +const setTimeout = (callback, timeout, ...args) => { + const timer = new Timeout(callback, timeout, ...args); + return timer.id; +} +const everyTick = (callback, ...args) => { + const timer = new EveryTick(callback, ...args); + return timer.id; +} +const nextTick = (callback, ...args) => { + const timer = new NextTick(callback, ...args); + return timer.id; +} + +globalThis.setInterval = setInterval; +globalThis.setTimeout = setTimeout; +globalThis.time = time; +globalThis.timeEnd = timeEnd; + +cppBindings.registerExport(cppBindings.BindingExport.EVERY_TICK, everyTick); +cppBindings.registerExport(cppBindings.BindingExport.NEXT_TICK, nextTick); +cppBindings.registerExport(cppBindings.BindingExport.SET_INTERVAL, setInterval); +cppBindings.registerExport(cppBindings.BindingExport.SET_TIMEOUT, setTimeout); +cppBindings.registerExport(cppBindings.BindingExport.TIME, time); +cppBindings.registerExport(cppBindings.BindingExport.TIME_END, timeEnd); + +function removeTimer(timerID) { + const timer = Timer.getByID(timerID); + timer?.destroy(); +} + +cppBindings.registerExport(cppBindings.BindingExport.CLEAR_EVERY_TICK, removeTimer); +cppBindings.registerExport(cppBindings.BindingExport.CLEAR_INTERVAL, removeTimer); +cppBindings.registerExport(cppBindings.BindingExport.CLEAR_NEXT_TICK, removeTimer); +cppBindings.registerExport(cppBindings.BindingExport.CLEAR_TIMEOUT, removeTimer); +cppBindings.registerExport(cppBindings.BindingExport.CLEAR_TIMER, removeTimer); + +globalThis.clearInterval = removeTimer; +globalThis.clearTimeout = removeTimer; + +function tick() { + for (const timer of timers.values()) { + timer.tick(); + } +} +cppBindings.registerExport(cppBindings.BindingExport.TICK, tick); + +function timersCount() { + let everyTickCount = 0; + let timeoutCount = 0; + let intervalCount = 0; + + for (const [_, timer] of timers) { + if (!timer.once && !timer.interval) everyTickCount++; + else if (timer.once) timeoutCount++; + else intervalCount++; + } + + return Object.freeze({ + totalCount : timers.size, + everyTickCount, + timeoutCount, + intervalCount, + benchmarkCount: timeMap.size, + }); +} + +cppBindings.registerExport(cppBindings.BindingExport.TIMERS_COUNT, timersCount); // Needed for timer info and health methods diff --git a/shared/helpers/BindingHandler.h b/shared/helpers/BindingHandler.h index 616b1e33..21d39991 100644 --- a/shared/helpers/BindingHandler.h +++ b/shared/helpers/BindingHandler.h @@ -10,7 +10,9 @@ namespace js enum class BindingExport : uint8_t { // Methods + TICK, HASH, + TIMERS_COUNT, // Classes VECTOR3_CLASS, @@ -18,6 +20,19 @@ namespace js RGBA_CLASS, QUATERNION_CLASS, + // Timers + TIME, + TIME_END, + SET_INTERVAL, + SET_TIMEOUT, + EVERY_TICK, + NEXT_TICK, + CLEAR_INTERVAL, + CLEAR_TIMEOUT, + CLEAR_EVERY_TICK, + CLEAR_TIMER, + CLEAR_NEXT_TICK, + SIZE, }; From 41a310dcbc611844efef1cf41bcad7303ffb95d3 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Tue, 28 Jan 2025 15:31:36 +0100 Subject: [PATCH 11/22] ALTV-659: Fixed vector api --- shared/bindings/js/classes/vector2.js | 2 +- shared/bindings/js/classes/vector3.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/bindings/js/classes/vector2.js b/shared/bindings/js/classes/vector2.js index 6114be11..bd5d5756 100644 --- a/shared/bindings/js/classes/vector2.js +++ b/shared/bindings/js/classes/vector2.js @@ -44,7 +44,7 @@ export class Vector2 { return Math.sqrt(this.lengthSquared); } - get normalized() { + get normalize() { const length = this.length; if (length === 0) { return Vector2.zero; diff --git a/shared/bindings/js/classes/vector3.js b/shared/bindings/js/classes/vector3.js index 476e296a..c47281fa 100644 --- a/shared/bindings/js/classes/vector3.js +++ b/shared/bindings/js/classes/vector3.js @@ -49,7 +49,7 @@ export class Vector3 { return Math.sqrt(this.lengthSquared); } - get normalized() { + get normalize() { const length = this.length; if (length === 0) { return Vector3.zero; From 8b42b48f5766468c2e8d1fa76b319e269ca888d4 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Wed, 29 Jan 2025 11:37:07 +0100 Subject: [PATCH 12/22] ALTV-659: Fixed compiled bindings error, when script cannot find cpp file --- .gitignore | 2 +- client/CMakeLists.txt | 1 - client/src/CV8Resource.cpp | 2 ++ server/CMakeLists.txt | 9 ++++----- server/src/CNodeResourceImpl.cpp | 2 ++ shared/V8ResourceImpl.cpp | 28 ++++++++++++++++++++++++++++ shared/V8ResourceImpl.h | 27 +-------------------------- shared/cmake/GenerateBindings.cmake | 9 +-------- tools/convert-bindings.js | 2 +- 9 files changed, 40 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index 9e599696..00d8a16e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Generated *.gen shared/CompiledEnums.h -shared/CompiledBindings.cpp +shared/CompiledBindings.h shared/bindings-hashes.json node_modules/ diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2b37cd88..37bf2fc3 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -98,7 +98,6 @@ if(DYNAMIC_BUILD EQUAL 1) ${PROJECT_NAME} SHARED ${PROJECT_SOURCE_FILES} ${PROJECT_SHARED_FILES} - ../shared/CompiledBindings.cpp ) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 diff --git a/client/src/CV8Resource.cpp b/client/src/CV8Resource.cpp index b3706250..c34d0866 100644 --- a/client/src/CV8Resource.cpp +++ b/client/src/CV8Resource.cpp @@ -26,6 +26,8 @@ #include "workers/CWorker.h" +#include "CompiledBindings.h" + extern void StaticRequire(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index a20bccac..e1076175 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -140,10 +140,9 @@ link_directories( ) add_library( - ${PROJECT_NAME} SHARED - ${PROJECT_SOURCE_FILES} - ${PROJECT_SHARED_FILES} - ../shared/CompiledBindings.cpp + ${PROJECT_NAME} SHARED + ${PROJECT_SOURCE_FILES} + ${PROJECT_SHARED_FILES} ) add_dependencies(${PROJECT_NAME} alt-sdk js-bindings) @@ -159,4 +158,4 @@ endif (WIN32) if (UNIX) target_link_libraries(${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/deps/nodejs/lib/libnode.so) -endif (UNIX) +endif (UNIX) \ No newline at end of file diff --git a/server/src/CNodeResourceImpl.cpp b/server/src/CNodeResourceImpl.cpp index fba9cafa..6f28371e 100644 --- a/server/src/CNodeResourceImpl.cpp +++ b/server/src/CNodeResourceImpl.cpp @@ -8,6 +8,8 @@ #include "V8Module.h" #include "V8Helpers.h" +#include "CompiledBindings.h" + static void ResourceLoaded(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); diff --git a/shared/V8ResourceImpl.cpp b/shared/V8ResourceImpl.cpp index 28f9b5b2..d6971d91 100644 --- a/shared/V8ResourceImpl.cpp +++ b/shared/V8ResourceImpl.cpp @@ -19,6 +19,34 @@ using namespace alt; extern V8Class v8BaseObject; + +void V8ResourceImpl::TimerBenchmark() +{ + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope seal(isolate); + v8::Context::Scope scope(context.Get(isolate)); + + v8::Local timersCountFunc = GetBindingHandler()->GetBindingExport(js::BindingExport::TIMERS_COUNT); + v8::Local timersData = timersCountFunc->Call(GetContext(), v8::Undefined(isolate), 0, nullptr).ToLocalChecked(); + + if (!timersData->IsObject()) + { + Log::Error << GetResource()->GetName() << ": Cannot convert function return value to object!" << Log::Endl; + return; + } + + v8::Local obj = timersData->ToObject(GetContext()).ToLocalChecked(); + + int totalCount = js::GetV8ObjectValue(GetContext(), obj, "totalCount"); + int everyTickCount = js::GetV8ObjectValue(GetContext(), obj, "everyTickCount"); + int intervalCount = js::GetV8ObjectValue(GetContext(), obj, "intervalCount"); + int timeoutCount = js::GetV8ObjectValue(GetContext(), obj, "timeoutCount"); + + Log::Info << GetResource()->GetName() << ": " << totalCount << " running timers (" << everyTickCount << " EveryTick, " << intervalCount << " Interval, " << timeoutCount << " Timeout" + << ")" << Log::Endl; +} + bool V8ResourceImpl::Start() { baseObjectClass.Reset(isolate, v8BaseObject.JSValue(isolate, GetContext())); diff --git a/shared/V8ResourceImpl.h b/shared/V8ResourceImpl.h index 543727e4..4ee0c39c 100644 --- a/shared/V8ResourceImpl.h +++ b/shared/V8ResourceImpl.h @@ -221,32 +221,7 @@ class V8ResourceImpl : public alt::IResource::Impl return alt::ICore::Instance().CreateMValueFunction(impl); } - void TimerBenchmark() - { - v8::Locker locker(isolate); - v8::Isolate::Scope isolateScope(isolate); - v8::HandleScope seal(isolate); - v8::Context::Scope scope(context.Get(isolate)); - - v8::Local timersCountFunc = GetBindingHandler()->GetBindingExport(js::BindingExport::TIMERS_COUNT); - v8::Local timersData = timersCountFunc->Call(GetContext(), v8::Undefined(isolate), 0, nullptr).ToLocalChecked(); - - if (!timersData->IsObject()) - { - Log::Error << GetResource()->GetName() << ": Cannot convert function return value to object!" << Log::Endl; - return; - } - - v8::Local obj = timersData->ToObject(GetContext()).ToLocalChecked(); - - int totalCount = js::GetV8ObjectValue(GetContext(), obj, "totalCount"); - int everyTickCount = js::GetV8ObjectValue(GetContext(), obj, "everyTickCount"); - int intervalCount = js::GetV8ObjectValue(GetContext(), obj, "intervalCount"); - int timeoutCount = js::GetV8ObjectValue(GetContext(), obj, "timeoutCount"); - - Log::Info << GetResource()->GetName() << ": " << totalCount << " running timers (" << everyTickCount << " EveryTick, " << intervalCount << " Interval, " << timeoutCount << " Timeout" - << ")" << Log::Endl; - } + void TimerBenchmark(); void NotifyPoolUpdate(alt::IBaseObject* ent); diff --git a/shared/cmake/GenerateBindings.cmake b/shared/cmake/GenerateBindings.cmake index ed67b21f..07f03188 100644 --- a/shared/cmake/GenerateBindings.cmake +++ b/shared/cmake/GenerateBindings.cmake @@ -3,13 +3,6 @@ if(NOT BINDINGS_SCOPE) set(BINDINGS_SCOPE "SHARED") endif() -function(make_includable input_file output_file) - file(READ ${input_file} content) - set(delim "for_c++_include") - set(content "R\"${delim}(\n${content})${delim}\"") - file(WRITE ${output_file} "${content}") -endfunction(make_includable) - if (CMAKE_HOST_WIN32) add_custom_target(js-bindings call generate-bindings.bat ${BINDINGS_SCOPE} @@ -20,4 +13,4 @@ else() bash generate-bindings.sh ${BINDINGS_SCOPE} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) -endif() +endif() \ No newline at end of file diff --git a/tools/convert-bindings.js b/tools/convert-bindings.js index da100a67..ca2207ee 100644 --- a/tools/convert-bindings.js +++ b/tools/convert-bindings.js @@ -38,7 +38,7 @@ namespace js { const bindingTemplate = `{ "{BINDING_NAME}", js::Binding{ "{BINDING_NAME}", js::Binding::Scope::{BINDING_SCOPE}, { {BINDING_SRC} } } }`; // Result bindings output path -const outputPath = "shared/CompiledBindings.cpp"; +const outputPath = "shared/CompiledBindings.h"; const hashesOutputPath = "shared/bindings-hashes.json"; (async () => { From 530d73cd8a4ffb28b6ea2aca7e47a52d9fc85943 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Wed, 29 Jan 2025 14:32:18 +0100 Subject: [PATCH 13/22] ALTV-659: Refactored getBuiltinModule method --- shared/bindings/SharedCppBindings.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/shared/bindings/SharedCppBindings.cpp b/shared/bindings/SharedCppBindings.cpp index 9e23fccc..8e624746 100644 --- a/shared/bindings/SharedCppBindings.cpp +++ b/shared/bindings/SharedCppBindings.cpp @@ -58,19 +58,22 @@ static void GetBuiltinModule(const v8::FunctionCallbackInfo& info) V8_CHECK_ARGS_LEN(1); V8_ARG_TO_STRING(1, name); - auto altInternalMap = std::map({ - { "alt-client", "alt" }, - { "alt-server", "alt" }, - }); - if (altInternalMap.contains(name)) name = altInternalMap.at(name); - - if(!V8Module::Exists(name)) - { + static const std::unordered_map altInternalMap = { + {"alt-client", "alt"}, + {"alt-server", "alt"} + }; + + if (auto it = altInternalMap.find(name); it != altInternalMap.end()) { + name = it->second; + } + + if (!V8Module::Exists(name)) { V8_RETURN_NULL(); return; } - V8Module& mod = V8Module::Get(name); - V8_RETURN(mod.GetModuleExports(isolate)); + + auto exports = V8Module::Get(name).GetModuleExports(isolate); + V8_RETURN(exports); } extern V8Module From b8a657172bf0c0373f12cc1b1ee55063ac848b8a Mon Sep 17 00:00:00 2001 From: Potapenko Date: Wed, 29 Jan 2025 15:48:18 +0100 Subject: [PATCH 14/22] ALTV-659: Refactored resource code and fix cjs import --- client/src/CV8Resource.cpp | 3 +++ server/src/CNodeResourceImpl.cpp | 17 +++++++++-------- server/src/bindings/js/bootstrap.js | 17 +++++------------ shared/V8ResourceImpl.cpp | 4 +--- shared/V8ResourceImpl.h | 4 +--- 5 files changed, 19 insertions(+), 26 deletions(-) diff --git a/client/src/CV8Resource.cpp b/client/src/CV8Resource.cpp index c34d0866..80c8c78d 100644 --- a/client/src/CV8Resource.cpp +++ b/client/src/CV8Resource.cpp @@ -106,6 +106,9 @@ bool CV8ResourceImpl::Start() v8::Context::Scope context_scope(ctx); V8ResourceImpl::Initialize(); + V8ResourceImpl::Start(); + SetupScriptGlobals(ctx); + V8ResourceImpl::InitializeBindings(js::Binding::Scope::CLIENT, V8Module::Get("alt")); js::Binding bootstrapper = js::Binding::Get("client/bootstrap.js"); diff --git a/server/src/CNodeResourceImpl.cpp b/server/src/CNodeResourceImpl.cpp index 6f28371e..fba561e4 100644 --- a/server/src/CNodeResourceImpl.cpp +++ b/server/src/CNodeResourceImpl.cpp @@ -52,15 +52,9 @@ bool CNodeResourceImpl::Start() auto _context = GetContext(); v8::Context::Scope scope(_context); + V8ResourceImpl::SetupScriptGlobals(_context); V8ResourceImpl::Initialize(); - - std::vector args{ resource->GetName() }; - std::vector execArgs{ }; - auto flags = static_cast(node::EnvironmentFlags::kNoFlags); - node::ThreadId threadId = node::AllocateEnvironmentThreadId(); - auto inspector = node::GetInspectorParentHandle(runtime->GetParentEnv(), threadId, resource->GetName().c_str()); - - env = node::CreateEnvironment(nodeData, _context, args, execArgs, flags, threadId, std::move(inspector)); + V8ResourceImpl::Start(); V8ResourceImpl::InitializeBindings(js::Binding::Scope::SERVER, V8Module::Get("alt")); @@ -71,6 +65,13 @@ bool CNodeResourceImpl::Start() js::TemporaryGlobalExtension cppBindings(_context, "__cppBindings", V8Module::Get("cppBindings").GetModuleExports(isolate)); js::TemporaryGlobalExtension resourceLoaded(_context, "__resourceLoaded", v8::Function::New(_context, &ResourceLoaded).ToLocalChecked()); + std::vector args{ resource->GetName() }; + std::vector execArgs{ }; + auto flags = static_cast(node::EnvironmentFlags::kNoFlags); + node::ThreadId threadId = node::AllocateEnvironmentThreadId(); + auto inspector = node::GetInspectorParentHandle(runtime->GetParentEnv(), threadId, resource->GetName().c_str()); + + env = node::CreateEnvironment(nodeData, _context, args, execArgs, flags, threadId, std::move(inspector)); node::LoadEnvironment(env, bootstrapper.GetSource()); // Not sure it's needed anymore diff --git a/server/src/bindings/js/bootstrap.js b/server/src/bindings/js/bootstrap.js index 32a49ec7..6446fb59 100644 --- a/server/src/bindings/js/bootstrap.js +++ b/server/src/bindings/js/bootstrap.js @@ -49,16 +49,9 @@ const inspector = require('inspector'); // Get the path to the main file for this resource, and load it const _path = path.resolve(resource.path, resource.main); - _exports = await esmLoader.import(url.pathToFileURL(_path).toString(), '', {}); - /* No one used this and only caused problems for people using that function name, - so let's just remove it for now and see if anyone complains - if ('start' in _exports) { - const start = _exports.start; - if (typeof start === 'function') { - await start(); - } - } - */ + const pathStr = url.pathToFileURL(_path).toString(); + _exports = await esmLoader.import(pathStr, '', {}); + } catch (e) { console.error(e); } @@ -108,7 +101,7 @@ function setupImports(esmLoader) { const altLoader = { resolveSync(specifier, context, importAttributes) { - if (cppBindings.getBuiltinModule(specifier) !== null) + if (cppBindings.getBuiltinModule(specifier) != null) return { url: `${altModuleImportPrefix}:${specifier}`, shortCircuit: true @@ -191,7 +184,7 @@ function setupImports(esmLoader) { const _origModuleLoad = BuildInModule._load; BuildInModule._load = function _load(request, parent, isMain) { const altModule = cppBindings.getBuiltinModule(request); - if (altModule !== undefined) { + if (altModule != null) { return altModule; } diff --git a/shared/V8ResourceImpl.cpp b/shared/V8ResourceImpl.cpp index d6971d91..1fa659d8 100644 --- a/shared/V8ResourceImpl.cpp +++ b/shared/V8ResourceImpl.cpp @@ -756,10 +756,8 @@ static void GetExtraBootstrapFile(const v8::FunctionCallbackInfo& inf V8_RETURN_STRING(extraBootstrapFile); } -void V8ResourceImpl::SetupScriptGlobals() +void V8ResourceImpl::SetupScriptGlobals(v8::Local ctx) { - v8::Local ctx = context.Get(isolate); - ctx->Global()->Set(ctx, V8Helpers::JSValue("__setLogFunction"), v8::Function::New(ctx, &::SetLogFunction).ToLocalChecked()); ctx->Global()->Set(ctx, V8Helpers::JSValue("__printLog"), v8::Function::New(ctx, &::PrintLog).ToLocalChecked()); ctx->Global()->Set(ctx, V8Helpers::JSValue("__registerExtraBootstrapFile"), v8::Function::New(ctx, &::RegisterExtraBootstrapFile).ToLocalChecked()); diff --git a/shared/V8ResourceImpl.h b/shared/V8ResourceImpl.h index 4ee0c39c..842bdbbe 100644 --- a/shared/V8ResourceImpl.h +++ b/shared/V8ResourceImpl.h @@ -43,8 +43,6 @@ class V8ResourceImpl : public alt::IResource::Impl { V8Class::Initialize(isolate); V8Module::Initialize(isolate); - V8ResourceImpl::Start(); - V8ResourceImpl::SetupScriptGlobals(); } bool Start() override; @@ -268,7 +266,7 @@ class V8ResourceImpl : public alt::IResource::Impl logFunction.Reset(isolate, function); } - void SetupScriptGlobals(); + void SetupScriptGlobals(v8::Local ctx); static V8ResourceImpl* Get(v8::Local ctx) { From 693cdcb854bc7bad008eea97a7d8c44367e16699 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Thu, 30 Jan 2025 16:51:43 +0100 Subject: [PATCH 15/22] ALTV-659: Fixed meta props, update cpp sdk --- client/src/bindings/js/classes/blip.js | 4 + client/src/bindings/js/classes/checkpoint.js | 10 --- .../src/bindings/js/classes/virtualEntity.js | 26 +++--- .../bindings/js/classes/virtualEntityGroup.js | 31 +------ server/src/bindings/js/classes/baseObject.js | 10 +++ server/src/bindings/js/classes/blip.js | 14 +++ server/src/bindings/js/classes/checkpoint.js | 13 +-- server/src/bindings/js/classes/entity.js | 15 +--- .../src/bindings/js/classes/virtualEntity.js | 29 ++++--- .../bindings/js/classes/virtualEntityGroup.js | 1 - shared/bindings/BaseObject.cpp | 20 +++-- shared/bindings/BindingsMain.cpp | 20 +++-- shared/bindings/Blip.cpp | 85 ++++++++++++++++++- shared/bindings/Entity.cpp | 43 +++++++++- shared/bindings/VirtualEntity.cpp | 21 +++-- shared/bindings/VirtualEntityGroup.cpp | 62 ++++++++------ .../bindings/js/classes/sharedBaseObject.js | 11 +-- shared/bindings/js/classes/sharedEntity.js | 3 +- shared/bindings/js/metas.js | 80 +++++++++++++++++ shared/deps/cpp-sdk | 2 +- shared/helpers/BindingHandler.h | 10 +++ shared/helpers/JS.h | 20 +++++ 22 files changed, 390 insertions(+), 140 deletions(-) create mode 100644 client/src/bindings/js/classes/blip.js delete mode 100644 client/src/bindings/js/classes/checkpoint.js create mode 100644 server/src/bindings/js/classes/blip.js create mode 100644 shared/bindings/js/metas.js diff --git a/client/src/bindings/js/classes/blip.js b/client/src/bindings/js/classes/blip.js new file mode 100644 index 00000000..3b273792 --- /dev/null +++ b/client/src/bindings/js/classes/blip.js @@ -0,0 +1,4 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); + +extendClassWithProperties(alt.Blip, null, SharedBaseObject); \ No newline at end of file diff --git a/client/src/bindings/js/classes/checkpoint.js b/client/src/bindings/js/classes/checkpoint.js deleted file mode 100644 index 8dc46c6c..00000000 --- a/client/src/bindings/js/classes/checkpoint.js +++ /dev/null @@ -1,10 +0,0 @@ -const { assert } = requireBinding("shared/utils.js"); -const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); - -class Checkpoint{ - static get count() { - return alt.Checkpoint.all.length; - } -} - -extendClassWithProperties(alt.Checkpoint, null, Checkpoint); \ No newline at end of file diff --git a/client/src/bindings/js/classes/virtualEntity.js b/client/src/bindings/js/classes/virtualEntity.js index 8d17d741..b3d35001 100644 --- a/client/src/bindings/js/classes/virtualEntity.js +++ b/client/src/bindings/js/classes/virtualEntity.js @@ -1,20 +1,24 @@ -const { assert } = requireBinding("shared/utils.js"); const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); -const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); class VirtualEntity { - setStreamSyncedMeta(key, value) { - if (isObject(key)) { - this.setMultipleStreamSyncedMetaData(key); - return; - } + getSyncedMeta(key) { + return this.syncedMeta[key]; + } + + getSyncedMetaKeys() { + const keys = Object.keys(this.syncedMeta); + return Object.freeze(keys); + } - this.streamSyncedMeta[key] = value; + getStreamSyncedMeta(key) { + return this.streamSyncedMeta[key]; } - deleteStreamSyncedMeta(key) { - delete this.streamSyncedMeta[key]; + getStreamSyncedMetaKeys() { + const keys = Object.keys(this.streamSyncedMeta); + return Object.freeze(keys); } } -extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedEntity); \ No newline at end of file +extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedBaseObject); \ No newline at end of file diff --git a/client/src/bindings/js/classes/virtualEntityGroup.js b/client/src/bindings/js/classes/virtualEntityGroup.js index e95d2ddb..88d25285 100644 --- a/client/src/bindings/js/classes/virtualEntityGroup.js +++ b/client/src/bindings/js/classes/virtualEntityGroup.js @@ -1,31 +1,4 @@ -const { assert } = requireBinding("shared/utils.js"); const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); -class VirtualEntityGroup { - hasMeta(key) { - return this.meta[key] !== undefined; - } - - getMeta(key) { - return this.meta[key]; - } - - getMetaKeys() { - return Object.keys(this.meta); - } - - setMeta(key, value) { - if (isObject(key)) { - this.setMultipleMetaData(key); - return; - } - - this.meta[key] = value; - } - - deleteMeta(key) { - delete this.meta[key]; - } -} - -extendClassWithProperties(alt.VirtualEntityGroup, null, VirtualEntityGroup); \ No newline at end of file +extendClassWithProperties(alt.VirtualEntityGroup, null, SharedBaseObject); \ No newline at end of file diff --git a/server/src/bindings/js/classes/baseObject.js b/server/src/bindings/js/classes/baseObject.js index 55a39d29..9470a3d0 100644 --- a/server/src/bindings/js/classes/baseObject.js +++ b/server/src/bindings/js/classes/baseObject.js @@ -1,4 +1,14 @@ const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); +class BaseObject { + setSyncedMeta(key, value) { + this.syncedMeta[key] = value; + } + + deleteSyncedMeta(key) { + delete this.syncedMeta[key]; + } +} + extendClassWithProperties(alt.BaseObject, null, SharedBaseObject); \ No newline at end of file diff --git a/server/src/bindings/js/classes/blip.js b/server/src/bindings/js/classes/blip.js new file mode 100644 index 00000000..34703ba5 --- /dev/null +++ b/server/src/bindings/js/classes/blip.js @@ -0,0 +1,14 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); + +class Blip { + setSyncedMeta(key, value) { + this.syncedMeta[key] = value; + } + + deleteSyncedMeta(key) { + delete this.syncedMeta[key]; + } +} + +extendClassWithProperties(alt.Blip, null, Blip, SharedBaseObject); \ No newline at end of file diff --git a/server/src/bindings/js/classes/checkpoint.js b/server/src/bindings/js/classes/checkpoint.js index 1a2ba590..59c9231a 100644 --- a/server/src/bindings/js/classes/checkpoint.js +++ b/server/src/bindings/js/classes/checkpoint.js @@ -1,18 +1,9 @@ -const { assert } = requireBinding("shared/utils.js"); -const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); class Checkpoint { - static get count() { - return alt.Checkpoint.all.length; - } setStreamSyncedMeta(key, value) { - if (isObject(key)) { - this.setMultipleStreamSyncedMetaData(key); - return; - } - this.streamSyncedMeta[key] = value; } @@ -21,4 +12,4 @@ class Checkpoint { } } -extendClassWithProperties(alt.Checkpoint, null, Checkpoint, SharedEntity); +extendClassWithProperties(alt.Checkpoint, null, Checkpoint, SharedEntity); \ No newline at end of file diff --git a/server/src/bindings/js/classes/entity.js b/server/src/bindings/js/classes/entity.js index 1cd8ab92..57384995 100644 --- a/server/src/bindings/js/classes/entity.js +++ b/server/src/bindings/js/classes/entity.js @@ -1,14 +1,12 @@ const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); -const { isObject } = requireBinding("shared/utils.js"); const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); class Entity { - setSyncedMeta(key, value) { - if (isObject(key)) { - this.setMultipleSyncedMetaData(key); - return; - } + get isSpawned() { + return this.scriptID != 0; + } + setSyncedMeta(key, value) { this.syncedMeta[key] = value; } @@ -17,11 +15,6 @@ class Entity { } setStreamSyncedMeta(key, value) { - if (isObject(key)) { - this.setMultipleStreamSyncedMetaData(key); - return; - } - this.streamSyncedMeta[key] = value; } diff --git a/server/src/bindings/js/classes/virtualEntity.js b/server/src/bindings/js/classes/virtualEntity.js index d13e53b6..732d12be 100644 --- a/server/src/bindings/js/classes/virtualEntity.js +++ b/server/src/bindings/js/classes/virtualEntity.js @@ -1,30 +1,39 @@ -const { assert } = requireBinding("shared/utils.js"); const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); class VirtualEntity { - hasSyncedMeta(key) { - return this.syncedMeta[key] !== undefined; - } - getSyncedMeta(key) { return this.syncedMeta[key]; } - getSyncedMetaKeys() { - return Object.keys(this.syncedMeta); + setSyncedMeta(key, value) { + this.syncedMeta[key] = value; + } + + deleteSyncedMeta(key) { + delete this.syncedMeta[key]; } - hasStreamSyncedMeta(key) { - return this.streamSyncedMeta[key] !== undefined; + getSyncedMetaKeys() { + const keys = Object.keys(this.syncedMeta); + return Object.freeze(keys); } getStreamSyncedMeta(key) { return this.streamSyncedMeta[key]; } + setStreamSyncedMeta(key, value) { + this.streamSyncedMeta[key] = value; + } + + deleteStreamSyncedMeta(key) { + delete this.streamSyncedMeta[key]; + } + getStreamSyncedMetaKeys() { - return Object.keys(this.streamSyncedMeta); + const keys = Object.keys(this.streamSyncedMeta); + return Object.freeze(keys); } } diff --git a/server/src/bindings/js/classes/virtualEntityGroup.js b/server/src/bindings/js/classes/virtualEntityGroup.js index 06d766f1..88d25285 100644 --- a/server/src/bindings/js/classes/virtualEntityGroup.js +++ b/server/src/bindings/js/classes/virtualEntityGroup.js @@ -1,4 +1,3 @@ -const { assert } = requireBinding("shared/utils.js"); const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); diff --git a/shared/bindings/BaseObject.cpp b/shared/bindings/BaseObject.cpp index 6ba26969..9cd0cdb4 100644 --- a/shared/bindings/BaseObject.cpp +++ b/shared/bindings/BaseObject.cpp @@ -34,31 +34,40 @@ static void ValidGetter(v8::Local, const v8::PropertyCallbackInfo(); + auto value = V8Helpers::MValueToV8(obj->GetMetaData(ctx.GetProperty())); ctx.Return(value); } static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); - alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); + obj->SetMetaData(ctx.GetProperty(), value); } static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + if(!obj->HasMetaData(ctx.GetProperty())) { ctx.Return(V8Helpers::JSValue(false)); return; } - alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + obj->DeleteMetaData(ctx.GetProperty()); ctx.Return(V8Helpers::JSValue(true)); } static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto keys = V8Helpers::JSValue(obj->GetMetaDataKeys()); ctx.Return(keys); } @@ -134,6 +143,7 @@ extern V8Class v8BaseObject("BaseObject", V8Helpers::SetAccessor(isolate, tpl, "type"); V8Helpers::SetAccessor(isolate, tpl, "valid", &ValidGetter); V8Helpers::SetAccessor(isolate, tpl, "id"); + #ifdef ALT_CLIENT_API V8Helpers::SetAccessor(isolate, tpl, "isRemote"); V8Helpers::SetAccessor(isolate, tpl, "remoteID"); diff --git a/shared/bindings/BindingsMain.cpp b/shared/bindings/BindingsMain.cpp index f5d7cc06..cb492645 100644 --- a/shared/bindings/BindingsMain.cpp +++ b/shared/bindings/BindingsMain.cpp @@ -135,18 +135,14 @@ static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) static void SyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - if (!ctx.CheckParent()) return; - auto* obj = ctx.GetParent(); - auto value = V8Helpers::MValueToV8(obj->GetSyncedMetaData(ctx.GetProperty())); + auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetSyncedMetaData(ctx.GetProperty())); ctx.Return(value); } static void SyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - if (!ctx.CheckParent()) return; - auto* obj = ctx.GetParent(); - auto value = V8Helpers::JSValue(obj->GetSyncedMetaDataKeys()); - ctx.Return(value); + auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetSyncedMetaDataKeys()); + ctx.Return(keys); } static void Log(const v8::FunctionCallbackInfo& info) @@ -424,6 +420,16 @@ extern V8Module V8Helpers::RegisterBindingExport(exports, "clearNextTick", js::BindingExport::CLEAR_NEXT_TICK); V8Helpers::RegisterBindingExport(exports, "clearTimeout", js::BindingExport::CLEAR_TIMEOUT); V8Helpers::RegisterBindingExport(exports, "clearTimer", js::BindingExport::CLEAR_TIMER); + + // Metas + V8Helpers::RegisterBindingExport(exports, "getMeta", js::BindingExport::GET_META); + V8Helpers::RegisterBindingExport(exports, "hasMeta", js::BindingExport::HAS_META); + V8Helpers::RegisterBindingExport(exports, "setMeta", js::BindingExport::SET_META); + V8Helpers::RegisterBindingExport(exports, "deleteMeta", js::BindingExport::DELETE_META); + V8Helpers::RegisterBindingExport(exports, "getMetaKeys", js::BindingExport::GET_META_KEYS); + V8Helpers::RegisterBindingExport(exports, "getSyncedMeta", js::BindingExport::GET_SYNCED_META); + V8Helpers::RegisterBindingExport(exports, "hasSyncedMeta", js::BindingExport::HAS_SYNCED_META); + V8Helpers::RegisterBindingExport(exports, "getSyncedMetaKeys", js::BindingExport::GET_SYNCED_META_KEYS); } V8_OBJECT_SET_STRING(exports, "version", alt::ICore::Instance().GetVersion()); diff --git a/shared/bindings/Blip.cpp b/shared/bindings/Blip.cpp index 7bf394f1..aef4dd2e 100644 --- a/shared/bindings/Blip.cpp +++ b/shared/bindings/Blip.cpp @@ -5,6 +5,61 @@ using namespace alt; +static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetMetaData(ctx.GetProperty())); + ctx.Return(value); +} + +static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetMetaData(ctx.GetProperty(), value); +} + +static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + if(!obj->HasMetaData(ctx.GetProperty())) + { + ctx.Return(V8Helpers::JSValue(false)); + return; + } + + obj->DeleteMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); +} + +static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto keys = V8Helpers::JSValue(obj->GetMetaDataKeys()); + ctx.Return(keys); +} + +static void SyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); +} + +static void SyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetSyncedMetaDataKeys()); + ctx.Return(value); +} + static void ToString(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); @@ -60,6 +115,29 @@ static void ConstructorAreaBlip(const v8::FunctionCallbackInfo& info) #endif #ifdef ALT_SERVER_API +static void SyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetSyncedMetaData(ctx.GetProperty(), value); +} + +static void SyncedMetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + if(!obj->HasSyncedMetaData(ctx.GetProperty())) + { + ctx.Return(V8Helpers::JSValue(false)); + return; + } + + obj->DeleteSyncedMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); +} + static void ConstructorRadiusBlip(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -118,7 +196,7 @@ static void ConstructorPointBlip(const v8::FunctionCallbackInfo& info if(info[0]->IsObject()) cls = V8Helpers::GetObjectClass(info[0].As()); - if(cls == V8Class::ObjectClass::VECTOR3) // TODO: we should allow IVector3 + if(cls == V8Class::ObjectClass::VECTOR3 || resource->IsVector3(info[0])) // TODO: we should allow IVector3 { V8_ARG_TO_VECTOR3(1, pos); V8_ARG_TO_BOOLEAN(2, global); @@ -327,6 +405,9 @@ extern V8Class v8Blip("Blip", V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); + V8Helpers::SetDynamicProperty(isolate, tpl, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); + V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, nullptr, nullptr, SyncedMetaEnumerator); + V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); #ifdef ALT_CLIENT_API V8Helpers::SetStaticMethod(isolate, tpl, "getByRemoteID", StaticGetByRemoteId); @@ -380,6 +461,8 @@ extern V8Class v8Blip("Blip", #endif #ifdef ALT_SERVER_API + V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, SyncedMetaSetter, SyncedMetaDeleter, SyncedMetaEnumerator); + V8Helpers::SetAccessor(isolate, tpl, "isGlobal"); V8Helpers::SetAccessor(isolate, tpl, "targets", &GetTargets); V8Helpers::SetMethod(isolate, tpl, "addTarget", &AddTargetPlayer); diff --git a/shared/bindings/Entity.cpp b/shared/bindings/Entity.cpp index 97244a7c..e1213ab3 100644 --- a/shared/bindings/Entity.cpp +++ b/shared/bindings/Entity.cpp @@ -8,6 +8,22 @@ using namespace alt; +static void SyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetSyncedMetaData(ctx.GetProperty())); + ctx.Return(value); +} + +static void SyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::JSValue(obj->GetSyncedMetaDataKeys()); + ctx.Return(value); +} + static void StreamSyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { if (!ctx.CheckParent()) return; @@ -24,7 +40,30 @@ static void StreamSyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorCo ctx.Return(value); } + #ifdef ALT_SERVER_API +static void SyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::V8ToMValue(ctx.GetValue()); + obj->SetSyncedMetaData(ctx.GetProperty(), value); +} + +static void SyncedMetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) +{ + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + if(!obj->HasSyncedMetaData(ctx.GetProperty())) + { + ctx.Return(V8Helpers::JSValue(false)); + return; + } + + obj->DeleteSyncedMetaData(ctx.GetProperty()); + ctx.Return(V8Helpers::JSValue(true)); +} static void StreamSyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) { @@ -253,7 +292,7 @@ extern V8Class v8Entity("Entity", V8Helpers::SetAccessor(isolate, tpl, "frozen"); - // Client will share only get and enumerate + V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, nullptr, nullptr, SyncedMetaEnumerator); V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, nullptr, nullptr, StreamSyncedMetaEnumerator); #ifdef ALT_SERVER_API @@ -262,7 +301,7 @@ extern V8Class v8Entity("Entity", V8Helpers::SetAccessor(isolate, tpl, "visible"); V8Helpers::SetAccessor(isolate, tpl, "streamed"); - // Server has also setter and deleter + V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, SyncedMetaSetter, SyncedMetaDeleter, SyncedMetaEnumerator); V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, StreamSyncedMetaSetter, StreamSyncedMetaDeleter, StreamSyncedMetaEnumerator); V8Helpers::SetMethod(isolate, tpl, "setNetOwner", SetNetOwner); diff --git a/shared/bindings/VirtualEntity.cpp b/shared/bindings/VirtualEntity.cpp index e4f3a5d6..1253853c 100644 --- a/shared/bindings/VirtualEntity.cpp +++ b/shared/bindings/VirtualEntity.cpp @@ -44,31 +44,40 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetMetaData(ctx.GetProperty())); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetMetaData(ctx.GetProperty())); ctx.Return(value); } static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); - alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); + obj->SetMetaData(ctx.GetProperty(), value); } static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + if(!obj->HasMetaData(ctx.GetProperty())) { ctx.Return(V8Helpers::JSValue(false)); return; } - alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + obj->DeleteMetaData(ctx.GetProperty()); ctx.Return(V8Helpers::JSValue(true)); } static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto keys = V8Helpers::JSValue(obj->GetMetaDataKeys()); ctx.Return(keys); } @@ -89,7 +98,6 @@ static void StreamSyncedMetaEnumerator(js::internal::DynamicPropertyEnumeratorCo } #ifdef ALT_SERVER_API - static void StreamSyncedMetaSetter(js::internal::DynamicPropertySetterContext& ctx) { if (!ctx.CheckParent()) return; @@ -148,7 +156,6 @@ extern V8Class v8VirtualEntity("VirtualEntity", V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, nullptr, nullptr, StreamSyncedMetaEnumerator); #ifdef ALT_SERVER_API - // Server has also setter and deleter V8Helpers::SetDynamicProperty(isolate, tpl, "streamSyncedMeta", StreamSyncedMetaGetter, StreamSyncedMetaSetter, StreamSyncedMetaDeleter, StreamSyncedMetaEnumerator); #endif // ALT_SERVER_API diff --git a/shared/bindings/VirtualEntityGroup.cpp b/shared/bindings/VirtualEntityGroup.cpp index 2c68ada6..74f8b734 100644 --- a/shared/bindings/VirtualEntityGroup.cpp +++ b/shared/bindings/VirtualEntityGroup.cpp @@ -3,57 +3,67 @@ using namespace alt; -static void Constructor(const v8::FunctionCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - V8_CHECK_CONSTRUCTOR(); - V8_CHECK_ARGS_LEN(1); - - V8_CHECK(info[0]->IsNumber(), "number expected"); - V8_ARG_TO_UINT(1, maxEntitiesInStream); - - IVirtualEntityGroup* virtualEntityGroup = ICore::Instance().CreateVirtualEntityGroup(maxEntitiesInStream); - - V8_BIND_BASE_OBJECT(virtualEntityGroup, "Failed to create virtual entity group"); -} - -static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_GET_ISOLATE_CONTEXT_RESOURCE(); - - V8_RETURN(resource->GetAllVirtualEntityGroups()); -} - static void MetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { - auto value = V8Helpers::MValueToV8(alt::ICore::Instance().GetMetaData(ctx.GetProperty())); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + auto value = V8Helpers::MValueToV8(obj->GetMetaData(ctx.GetProperty())); ctx.Return(value); } static void MetaSetter(js::internal::DynamicPropertySetterContext& ctx) { + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); alt::MValue value = V8Helpers::V8ToMValue(ctx.GetValue()); - alt::ICore::Instance().SetMetaData(ctx.GetProperty(), value); + obj->SetMetaData(ctx.GetProperty(), value); } static void MetaDeleter(js::internal::DynamicPropertyDeleterContext& ctx) { - if(!alt::ICore::Instance().HasMetaData(ctx.GetProperty())) + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + if(!obj->HasMetaData(ctx.GetProperty())) { ctx.Return(V8Helpers::JSValue(false)); return; } - alt::ICore::Instance().DeleteMetaData(ctx.GetProperty()); + obj->DeleteMetaData(ctx.GetProperty()); ctx.Return(V8Helpers::JSValue(true)); } static void MetaEnumerator(js::internal::DynamicPropertyEnumeratorContext& ctx) { - auto keys = V8Helpers::JSValue(alt::ICore::Instance().GetMetaDataKeys()); + if (!ctx.CheckParent()) return; + auto* obj = ctx.GetParent(); + + auto keys = V8Helpers::JSValue(obj->GetMetaDataKeys()); ctx.Return(keys); } +static void Constructor(const v8::FunctionCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + V8_CHECK_CONSTRUCTOR(); + V8_CHECK_ARGS_LEN(1); + + V8_CHECK(info[0]->IsNumber(), "number expected"); + V8_ARG_TO_UINT(1, maxEntitiesInStream); + + IVirtualEntityGroup* virtualEntityGroup = ICore::Instance().CreateVirtualEntityGroup(maxEntitiesInStream); + + V8_BIND_BASE_OBJECT(virtualEntityGroup, "Failed to create virtual entity group"); +} + +static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo& info) +{ + V8_GET_ISOLATE_CONTEXT_RESOURCE(); + + V8_RETURN(resource->GetAllVirtualEntityGroups()); +} + extern V8Class v8BaseObject; extern V8Class v8VirtualEntityGroup("VirtualEntityGroup", v8BaseObject, diff --git a/shared/bindings/js/classes/sharedBaseObject.js b/shared/bindings/js/classes/sharedBaseObject.js index 4c343d93..9a1a9ee9 100644 --- a/shared/bindings/js/classes/sharedBaseObject.js +++ b/shared/bindings/js/classes/sharedBaseObject.js @@ -10,11 +10,6 @@ export class SharedBaseObject { } setMeta(key, value) { - if (isObject(key)) { - this.setMultipleMetaData(key); - return; - } - this.meta[key] = value; } @@ -23,7 +18,8 @@ export class SharedBaseObject { } getMetaDataKeys() { - return Object.keys(this.meta); + const keys = Object.keys(this.meta); + return Object.freeze(keys); } getSyncedMeta(key) { @@ -35,6 +31,7 @@ export class SharedBaseObject { } getSyncedMetaKeys() { - return Object.keys(this.syncedMeta); + const keys = Object.keys(this.syncedMeta); + return Object.freeze(keys); } } diff --git a/shared/bindings/js/classes/sharedEntity.js b/shared/bindings/js/classes/sharedEntity.js index b9e1ee7d..2271120f 100644 --- a/shared/bindings/js/classes/sharedEntity.js +++ b/shared/bindings/js/classes/sharedEntity.js @@ -8,6 +8,7 @@ export class SharedEntity { } getStreamSyncedMetaKeys() { - return Object.keys(this.streamSyncedMeta); + const keys = Object.keys(this.streamSyncedMeta); + return Object.freeze(keys); } } \ No newline at end of file diff --git a/shared/bindings/js/metas.js b/shared/bindings/js/metas.js new file mode 100644 index 00000000..7aeace18 --- /dev/null +++ b/shared/bindings/js/metas.js @@ -0,0 +1,80 @@ +/** + * + * @param {string} key + */ +function deleteMeta(key) { + delete alt.meta[key]; +} + +/** + * + * @param {string} key + * @returns unknown + */ +function getMeta(key) { + return alt.meta[key]; +} + +/** + * + * @returns string[] + */ +function getMetaKeys() { + const keys = Object.keys(alt.meta); + return Object.freeze(keys); +} + +/** + * + * @param {string} key + * @returns boolean + */ +function hasMeta(key) { + return alt.meta[key] !== undefined; +} + +/** + * + * @param {string} key + * @param {unknown} value + */ +function setMeta(key, value) { + alt.meta[key] = value; +} + +/** + * + * @param {string} key + * @returns unknown + */ +function getSyncedMeta(key) { + return alt.syncedMeta[key]; +} + +/** + * + * @returns string[] + */ +function getSyncedMetaKeys() { + const keys = Object.keys(alt.syncedMeta); + return Object.freeze(keys); +} + +/** + * + * @param {string} key + * @returns boolean + */ +function hasSyncedMeta(key) { + return alt.syncedMeta[key] !== undefined; +} + +cppBindings.registerExport(cppBindings.BindingExport.HAS_META, hasMeta); +cppBindings.registerExport(cppBindings.BindingExport.GET_META, getMeta); +cppBindings.registerExport(cppBindings.BindingExport.SET_META, setMeta); +cppBindings.registerExport(cppBindings.BindingExport.DELETE_META, deleteMeta); +cppBindings.registerExport(cppBindings.BindingExport.GET_META_KEYS, getMetaKeys); + +cppBindings.registerExport(cppBindings.BindingExport.GET_SYNCED_META, getSyncedMeta); +cppBindings.registerExport(cppBindings.BindingExport.HAS_SYNCED_META, hasSyncedMeta); +cppBindings.registerExport(cppBindings.BindingExport.GET_SYNCED_META_KEYS, getSyncedMetaKeys); \ No newline at end of file diff --git a/shared/deps/cpp-sdk b/shared/deps/cpp-sdk index 000b5d5b..81e85576 160000 --- a/shared/deps/cpp-sdk +++ b/shared/deps/cpp-sdk @@ -1 +1 @@ -Subproject commit 000b5d5b9868d3491274e65884071d56877d1b93 +Subproject commit 81e855762a701dd47fedb5c9a48d7799a11b1863 diff --git a/shared/helpers/BindingHandler.h b/shared/helpers/BindingHandler.h index 21d39991..7e04be36 100644 --- a/shared/helpers/BindingHandler.h +++ b/shared/helpers/BindingHandler.h @@ -33,6 +33,16 @@ namespace js CLEAR_TIMER, CLEAR_NEXT_TICK, + // Metas + GET_META, + HAS_META, + SET_META, + DELETE_META, + GET_META_KEYS, + GET_SYNCED_META, + HAS_SYNCED_META, + GET_SYNCED_META_KEYS, + SIZE, }; diff --git a/shared/helpers/JS.h b/shared/helpers/JS.h index b02fd27c..002e8512 100644 --- a/shared/helpers/JS.h +++ b/shared/helpers/JS.h @@ -98,4 +98,24 @@ namespace js // Fallback if type doesn't match return defaultValue; } + + inline std::vector GetV8ObjectKeys(v8::Local context, v8::Local object) + { + std::vector keys; + v8::MaybeLocal maybePropNames = object->GetPropertyNames(context, + v8::KeyCollectionMode::kOwnOnly, + (v8::PropertyFilter)(v8::PropertyFilter::ONLY_ENUMERABLE | v8::PropertyFilter::SKIP_SYMBOLS), + v8::IndexFilter::kIncludeIndices, + v8::KeyConversionMode::kConvertToString); + v8::Local propNames; + if(!maybePropNames.ToLocal(&propNames)) return keys; + for(uint32_t i = 0; i < propNames->Length(); i++) + { + v8::MaybeLocal maybeKey = propNames->Get(context, i); + v8::Local key; + if(!maybeKey.ToLocal(&key)) continue; + keys.push_back(V8Helpers::CppValue(key.As())); + } + return keys; + } } \ No newline at end of file From d25e07b91aaf630ab3f469676ff666939494b977 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 31 Jan 2025 10:37:08 +0100 Subject: [PATCH 16/22] ALTV-659: Fixed vector api --- shared/bindings/js/classes/vector2.js | 2 +- shared/bindings/js/classes/vector3.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/bindings/js/classes/vector2.js b/shared/bindings/js/classes/vector2.js index bd5d5756..b92752fd 100644 --- a/shared/bindings/js/classes/vector2.js +++ b/shared/bindings/js/classes/vector2.js @@ -44,7 +44,7 @@ export class Vector2 { return Math.sqrt(this.lengthSquared); } - get normalize() { + normalize() { const length = this.length; if (length === 0) { return Vector2.zero; diff --git a/shared/bindings/js/classes/vector3.js b/shared/bindings/js/classes/vector3.js index c47281fa..59c5282e 100644 --- a/shared/bindings/js/classes/vector3.js +++ b/shared/bindings/js/classes/vector3.js @@ -49,7 +49,7 @@ export class Vector3 { return Math.sqrt(this.lengthSquared); } - get normalize() { + normalize() { const length = this.length; if (length === 0) { return Vector3.zero; From 3f29c50c6cf5e8718bbc66ed5d4973d4dcb056ff Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 31 Jan 2025 11:23:26 +0100 Subject: [PATCH 17/22] ALTV-659: Fixed browser bindings --- client/src/bindings/js/bootstrap.js | 2 +- .../src/bindings/{browser/Console.js => js/browser/console.js} | 2 +- .../{browser/TextDecoder.js => js/browser/text-decoder.js} | 0 .../{browser/TextEncoder.js => js/browser/text-encoder.js} | 0 client/src/bindings/{browser/Timers.js => js/browser/timers.js} | 0 server/src/bindings/js/bootstrap.js | 2 +- shared/bindings/js/logging.js | 2 +- 7 files changed, 4 insertions(+), 4 deletions(-) rename client/src/bindings/{browser/Console.js => js/browser/console.js} (87%) rename client/src/bindings/{browser/TextDecoder.js => js/browser/text-decoder.js} (100%) rename client/src/bindings/{browser/TextEncoder.js => js/browser/text-encoder.js} (100%) rename client/src/bindings/{browser/Timers.js => js/browser/timers.js} (100%) diff --git a/client/src/bindings/js/bootstrap.js b/client/src/bindings/js/bootstrap.js index f3368667..8bfe4e32 100644 --- a/client/src/bindings/js/bootstrap.js +++ b/client/src/bindings/js/bootstrap.js @@ -3,7 +3,7 @@ import * as alt from "alt"; import * as native from "natives"; // Load the global bindings code -__setLogFunction(genericLog); +__setLogFunction(alt.Utils.genericLog); const extraBootstrapFile = __getExtraBootstrapFile(); if(extraBootstrapFile.length !== 0) new Function("alt", "native", extraBootstrapFile)(alt, native); diff --git a/client/src/bindings/browser/Console.js b/client/src/bindings/js/browser/console.js similarity index 87% rename from client/src/bindings/browser/Console.js rename to client/src/bindings/js/browser/console.js index e04c2071..b0c29eb2 100644 --- a/client/src/bindings/browser/Console.js +++ b/client/src/bindings/js/browser/console.js @@ -7,4 +7,4 @@ globalThis.console.error = alt.logError; globalThis.console.info = alt.log; globalThis.console.debug = alt.logDebug; globalThis.console.time = alt.time; -globalThis.console.timeEnd = alt.timeEnd; +globalThis.console.timeEnd = alt.timeEnd; \ No newline at end of file diff --git a/client/src/bindings/browser/TextDecoder.js b/client/src/bindings/js/browser/text-decoder.js similarity index 100% rename from client/src/bindings/browser/TextDecoder.js rename to client/src/bindings/js/browser/text-decoder.js diff --git a/client/src/bindings/browser/TextEncoder.js b/client/src/bindings/js/browser/text-encoder.js similarity index 100% rename from client/src/bindings/browser/TextEncoder.js rename to client/src/bindings/js/browser/text-encoder.js diff --git a/client/src/bindings/browser/Timers.js b/client/src/bindings/js/browser/timers.js similarity index 100% rename from client/src/bindings/browser/Timers.js rename to client/src/bindings/js/browser/timers.js diff --git a/server/src/bindings/js/bootstrap.js b/server/src/bindings/js/bootstrap.js index 6446fb59..92e284f5 100644 --- a/server/src/bindings/js/bootstrap.js +++ b/server/src/bindings/js/bootstrap.js @@ -33,7 +33,7 @@ const inspector = require('inspector'); try { setupImports(esmLoader); - __setLogFunction(genericLog); + __setLogFunction(alt.Utils.genericLog); const config = alt.Resource.current.config; if (config.inspector) { diff --git a/shared/bindings/js/logging.js b/shared/bindings/js/logging.js index 3d18c332..ad8b89e7 100644 --- a/shared/bindings/js/logging.js +++ b/shared/bindings/js/logging.js @@ -3193,6 +3193,6 @@ function genericLog(type, ...args) { }); __printLog(type, ...logArgs); } -globalThis.genericLog = genericLog; +alt.Utils.genericLog = genericLog; alt.Utils.inspect = inspect; From 721581db0a916a936b441ba2030e751d21c2b979 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 31 Jan 2025 13:06:18 +0100 Subject: [PATCH 18/22] ALTV-659: Added necessary methods to VirtualEntity --- .../src/bindings/js/classes/virtualEntity.js | 23 ++----------------- .../src/bindings/js/classes/virtualEntity.js | 21 ++--------------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/client/src/bindings/js/classes/virtualEntity.js b/client/src/bindings/js/classes/virtualEntity.js index b3d35001..08a291c9 100644 --- a/client/src/bindings/js/classes/virtualEntity.js +++ b/client/src/bindings/js/classes/virtualEntity.js @@ -1,24 +1,5 @@ const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); -class VirtualEntity { - getSyncedMeta(key) { - return this.syncedMeta[key]; - } - - getSyncedMetaKeys() { - const keys = Object.keys(this.syncedMeta); - return Object.freeze(keys); - } - - getStreamSyncedMeta(key) { - return this.streamSyncedMeta[key]; - } - - getStreamSyncedMetaKeys() { - const keys = Object.keys(this.streamSyncedMeta); - return Object.freeze(keys); - } -} - -extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedBaseObject); \ No newline at end of file +extendClassWithProperties(alt.VirtualEntity, null, SharedBaseObject, SharedEntity); \ No newline at end of file diff --git a/server/src/bindings/js/classes/virtualEntity.js b/server/src/bindings/js/classes/virtualEntity.js index 732d12be..7d10cdba 100644 --- a/server/src/bindings/js/classes/virtualEntity.js +++ b/server/src/bindings/js/classes/virtualEntity.js @@ -1,11 +1,8 @@ const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); +const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); class VirtualEntity { - getSyncedMeta(key) { - return this.syncedMeta[key]; - } - setSyncedMeta(key, value) { this.syncedMeta[key] = value; } @@ -14,15 +11,6 @@ class VirtualEntity { delete this.syncedMeta[key]; } - getSyncedMetaKeys() { - const keys = Object.keys(this.syncedMeta); - return Object.freeze(keys); - } - - getStreamSyncedMeta(key) { - return this.streamSyncedMeta[key]; - } - setStreamSyncedMeta(key, value) { this.streamSyncedMeta[key] = value; } @@ -30,11 +18,6 @@ class VirtualEntity { deleteStreamSyncedMeta(key) { delete this.streamSyncedMeta[key]; } - - getStreamSyncedMetaKeys() { - const keys = Object.keys(this.streamSyncedMeta); - return Object.freeze(keys); - } } -extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedBaseObject); \ No newline at end of file +extendClassWithProperties(alt.VirtualEntity, null, VirtualEntity, SharedBaseObject, SharedEntity); \ No newline at end of file From ba7d86456c6900a3829176b3237a86ac48227ed2 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 31 Jan 2025 13:29:33 +0100 Subject: [PATCH 19/22] ALTV-659: Moved count static getter to js --- client/src/bindings/Audio.cpp | 6 ------ client/src/bindings/AudioOutput.cpp | 6 ------ client/src/bindings/Checkpoint.cpp | 6 ------ client/src/bindings/LocalObject.cpp | 6 ------ client/src/bindings/Object.cpp | 6 ------ client/src/bindings/Ped.cpp | 6 ------ client/src/bindings/Player.cpp | 6 ------ client/src/bindings/Vehicle.cpp | 8 +------- client/src/bindings/WeaponObject.cpp | 6 ------ client/src/bindings/WebView.cpp | 6 ------ client/src/bindings/js/classes/audio.js | 9 +++++++++ client/src/bindings/js/classes/audioOutput.js | 9 +++++++++ client/src/bindings/js/classes/checkpoint.js | 9 +++++++++ client/src/bindings/js/classes/localObject.js | 9 +++++++++ client/src/bindings/js/classes/object.js | 9 +++++++++ client/src/bindings/js/classes/ped.js | 9 +++++++++ client/src/bindings/js/classes/player.js | 9 +++++++++ client/src/bindings/js/classes/vehicle.js | 9 +++++++++ client/src/bindings/js/classes/weaponObject.js | 9 +++++++++ client/src/bindings/js/classes/webView.js | 9 +++++++++ server/src/bindings/Checkpoint.cpp | 6 ------ server/src/bindings/Object.cpp | 6 ------ server/src/bindings/Ped.cpp | 6 ------ server/src/bindings/Player.cpp | 6 ------ server/src/bindings/Vehicle.cpp | 6 ------ server/src/bindings/js/classes/checkpoint.js | 3 +++ server/src/bindings/js/classes/object.js | 9 +++++++++ server/src/bindings/js/classes/ped.js | 9 +++++++++ server/src/bindings/js/classes/player.js | 9 +++++++++ server/src/bindings/js/classes/vehicle.js | 9 +++++++++ shared/bindings/Blip.cpp | 6 ------ {client/src => shared}/bindings/js/classes/blip.js | 8 +++++++- 32 files changed, 137 insertions(+), 98 deletions(-) create mode 100644 client/src/bindings/js/classes/audio.js create mode 100644 client/src/bindings/js/classes/audioOutput.js create mode 100644 client/src/bindings/js/classes/checkpoint.js create mode 100644 client/src/bindings/js/classes/localObject.js create mode 100644 client/src/bindings/js/classes/object.js create mode 100644 client/src/bindings/js/classes/ped.js create mode 100644 client/src/bindings/js/classes/player.js create mode 100644 client/src/bindings/js/classes/vehicle.js create mode 100644 client/src/bindings/js/classes/weaponObject.js create mode 100644 client/src/bindings/js/classes/webView.js create mode 100644 server/src/bindings/js/classes/object.js create mode 100644 server/src/bindings/js/classes/ped.js create mode 100644 server/src/bindings/js/classes/player.js create mode 100644 server/src/bindings/js/classes/vehicle.js rename {client/src => shared}/bindings/js/classes/blip.js (52%) diff --git a/client/src/bindings/Audio.cpp b/client/src/bindings/Audio.cpp index ea29b65d..b92c6818 100644 --- a/client/src/bindings/Audio.cpp +++ b/client/src/bindings/Audio.cpp @@ -131,11 +131,6 @@ static void AllAudioGetter(v8::Local name, const v8::PropertyCallbac V8_RETURN(jsArr); } -static void AudioCountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::AUDIO).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -167,7 +162,6 @@ extern V8Class v8Audio("Audio", V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllAudioGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &AudioCountGetter); V8Helpers::SetMethod(isolate, tpl, "on", &On); V8Helpers::SetMethod(isolate, tpl, "off", &Off); diff --git a/client/src/bindings/AudioOutput.cpp b/client/src/bindings/AudioOutput.cpp index d7585f38..4e490ac7 100644 --- a/client/src/bindings/AudioOutput.cpp +++ b/client/src/bindings/AudioOutput.cpp @@ -22,11 +22,6 @@ static void AllAudioOutputGetter(v8::Local name, const v8::PropertyC V8_RETURN(resource->GetAllAudioOutputs()); } -static void AudioOutputCountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::AUDIO_OUTPUT).size()); -} - static void GetFilter(v8::Local, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -66,7 +61,6 @@ extern V8Class v8AudioOutput("AudioOutput", v8::Isolate* isolate = v8::Isolate::GetCurrent(); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllAudioOutputGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &AudioOutputCountGetter); V8Helpers::SetAccessor(isolate, tpl, "muted"); V8Helpers::SetAccessor(isolate, tpl, "volume"); diff --git a/client/src/bindings/Checkpoint.cpp b/client/src/bindings/Checkpoint.cpp index d997c82a..c6611284 100644 --- a/client/src/bindings/Checkpoint.cpp +++ b/client/src/bindings/Checkpoint.cpp @@ -117,11 +117,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllCheckpoints()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::CHECKPOINT).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -170,7 +165,6 @@ extern V8Class v8Checkpoint("Checkpoint", V8Helpers::SetAccessor(isolate, tpl, "scriptID"); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); V8Helpers::SetStaticMethod(isolate, tpl, "getByScriptID", StaticGetByScriptID); diff --git a/client/src/bindings/LocalObject.cpp b/client/src/bindings/LocalObject.cpp index 867304c5..5292e2df 100644 --- a/client/src/bindings/LocalObject.cpp +++ b/client/src/bindings/LocalObject.cpp @@ -124,11 +124,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllLocalObjects()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::LOCAL_OBJECT).size()); -} - static void AllWorldGetter(v8::Local name, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -262,7 +257,6 @@ extern V8Class v8LocalObject("LocalObject", V8Helpers::SetStaticMethod(isolate, tpl, "getByScriptID", StaticGetByScriptID); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "allWorld", &AllWorldGetter); diff --git a/client/src/bindings/Object.cpp b/client/src/bindings/Object.cpp index f2b4fb03..facb484a 100644 --- a/client/src/bindings/Object.cpp +++ b/client/src/bindings/Object.cpp @@ -21,11 +21,6 @@ static void ToString(const v8::FunctionCallbackInfo& info) V8_RETURN_STRING(ss.str()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::OBJECT).size()); -} - static void StreamedInGetter(v8::Local name, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -110,7 +105,6 @@ extern V8Class v8Object("Object", v8Entity, [](v8::Local t V8Helpers::SetStaticMethod(isolate, tpl, "getByRemoteID", StaticGetByRemoteId); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "streamedIn", &StreamedInGetter); V8Helpers::SetAccessor(isolate, tpl, "alpha"); diff --git a/client/src/bindings/Ped.cpp b/client/src/bindings/Ped.cpp index 2b7237a5..72a04375 100644 --- a/client/src/bindings/Ped.cpp +++ b/client/src/bindings/Ped.cpp @@ -12,11 +12,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllPeds()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::PED).size()); -} - static void StreamedInGetter(v8::Local name, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -96,7 +91,6 @@ extern V8Class v8Ped("Ped", v8Entity, [](v8::Local tpl) v8::Isolate* isolate = v8::Isolate::GetCurrent(); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "streamedIn", &StreamedInGetter); V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); V8Helpers::SetStaticMethod(isolate, tpl, "getByScriptID", StaticGetByScriptID); diff --git a/client/src/bindings/Player.cpp b/client/src/bindings/Player.cpp index 8f633c0f..a0ca8d8b 100644 --- a/client/src/bindings/Player.cpp +++ b/client/src/bindings/Player.cpp @@ -42,11 +42,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllPlayers()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::PLAYER).size()); -} - static void StreamedInGetter(v8::Local name, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -221,7 +216,6 @@ extern V8Class v8Player("Player", V8Helpers::SetStaticMethod(isolate, tpl, "getByRemoteID", StaticGetByRemoteId); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "streamedIn", &StreamedInGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "local", &LocalGetter); diff --git a/client/src/bindings/Vehicle.cpp b/client/src/bindings/Vehicle.cpp index 01bf7871..36d83b9d 100644 --- a/client/src/bindings/Vehicle.cpp +++ b/client/src/bindings/Vehicle.cpp @@ -69,11 +69,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllVehicles()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::VEHICLE).size()); -} - static void StreamedInGetter(v8::Local name, const v8::PropertyCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -313,7 +308,7 @@ static void SetupTransmission(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT(); V8_GET_THIS_BASE_OBJECT(vehicle, alt::IVehicle); - V8_CHECK_ARGS_LEN(0); + V8_CHECK_ARGS_LEN(0); vehicle->SetupTransmission(); } @@ -351,7 +346,6 @@ extern V8Class v8Vehicle("Vehicle", V8Helpers::SetStaticMethod(isolate, tpl, "getByRemoteID", StaticGetByRemoteId); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "streamedIn", &StreamedInGetter); // Common getters diff --git a/client/src/bindings/WeaponObject.cpp b/client/src/bindings/WeaponObject.cpp index fc28dbf3..34523ba0 100644 --- a/client/src/bindings/WeaponObject.cpp +++ b/client/src/bindings/WeaponObject.cpp @@ -106,11 +106,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllWeaponObjects()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetWeaponObjects().size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -155,7 +150,6 @@ extern V8Class v8WeaponObject("WeaponObject", v8::Isolate* isolate = v8::Isolate::GetCurrent(); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticMethod(isolate, tpl, "getByScriptID", StaticGetByScriptID); V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); diff --git a/client/src/bindings/WebView.cpp b/client/src/bindings/WebView.cpp index 86b3521a..c9341e71 100644 --- a/client/src/bindings/WebView.cpp +++ b/client/src/bindings/WebView.cpp @@ -444,11 +444,6 @@ static void AllWebviewGetter(v8::Local name, const v8::PropertyCallb V8_RETURN(jsArr); } -static void WebviewCountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::WEBVIEW).size()); -} - static void WebviewGpuAccelerationActive(v8::Local name, const v8::PropertyCallbackInfo& info) { V8_RETURN_BOOLEAN(alt::ICore::Instance().IsWebViewGpuAccelerationActive()); @@ -524,7 +519,6 @@ extern V8Class v8WebView("WebView", v8::Isolate* isolate = v8::Isolate::GetCurrent(); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllWebviewGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &WebviewCountGetter); V8Helpers::SetStaticAccessor(isolate, tpl, "gpuAccelerationActive", &WebviewGpuAccelerationActive); V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); diff --git a/client/src/bindings/js/classes/audio.js b/client/src/bindings/js/classes/audio.js new file mode 100644 index 00000000..2a409bd6 --- /dev/null +++ b/client/src/bindings/js/classes/audio.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Audio { + static get count() { + return alt.Audio.all.length; + } +} + +extendClassWithProperties(alt.Audio, null, Audio); \ No newline at end of file diff --git a/client/src/bindings/js/classes/audioOutput.js b/client/src/bindings/js/classes/audioOutput.js new file mode 100644 index 00000000..55e361b5 --- /dev/null +++ b/client/src/bindings/js/classes/audioOutput.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class AudioOutput { + static get count() { + return alt.AudioOutput.all.length; + } +} + +extendClassWithProperties(alt.AudioOutput, null, AudioOutput); \ No newline at end of file diff --git a/client/src/bindings/js/classes/checkpoint.js b/client/src/bindings/js/classes/checkpoint.js new file mode 100644 index 00000000..6dc9cb02 --- /dev/null +++ b/client/src/bindings/js/classes/checkpoint.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Checkpoint { + static get count() { + return alt.Checkpoint.all.length; + } +} + +extendClassWithProperties(alt.Checkpoint, null, Checkpoint); \ No newline at end of file diff --git a/client/src/bindings/js/classes/localObject.js b/client/src/bindings/js/classes/localObject.js new file mode 100644 index 00000000..e03ea451 --- /dev/null +++ b/client/src/bindings/js/classes/localObject.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class LocalObject { + static get count() { + return alt.LocalObject.all.length; + } +} + +extendClassWithProperties(alt.LocalObject, null, LocalObject); \ No newline at end of file diff --git a/client/src/bindings/js/classes/object.js b/client/src/bindings/js/classes/object.js new file mode 100644 index 00000000..87bfdd06 --- /dev/null +++ b/client/src/bindings/js/classes/object.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Object { + static get count() { + return alt.Object.all.length; + } +} + +extendClassWithProperties(alt.Object, null, Object); \ No newline at end of file diff --git a/client/src/bindings/js/classes/ped.js b/client/src/bindings/js/classes/ped.js new file mode 100644 index 00000000..b40b316b --- /dev/null +++ b/client/src/bindings/js/classes/ped.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Ped { + static get count() { + return alt.Ped.all.length; + } +} + +extendClassWithProperties(alt.Ped, null, Ped); \ No newline at end of file diff --git a/client/src/bindings/js/classes/player.js b/client/src/bindings/js/classes/player.js new file mode 100644 index 00000000..af61d6fe --- /dev/null +++ b/client/src/bindings/js/classes/player.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Player { + static get count() { + return alt.Player.all.length; + } +} + +extendClassWithProperties(alt.Player, null, Player); \ No newline at end of file diff --git a/client/src/bindings/js/classes/vehicle.js b/client/src/bindings/js/classes/vehicle.js new file mode 100644 index 00000000..80006080 --- /dev/null +++ b/client/src/bindings/js/classes/vehicle.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Vehicle { + static get count() { + return alt.Vehicle.all.length; + } +} + +extendClassWithProperties(alt.Vehicle, null, Vehicle); \ No newline at end of file diff --git a/client/src/bindings/js/classes/weaponObject.js b/client/src/bindings/js/classes/weaponObject.js new file mode 100644 index 00000000..ba5ae250 --- /dev/null +++ b/client/src/bindings/js/classes/weaponObject.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class WeaponObject { + static get count() { + return alt.WeaponObject.all.length; + } +} + +extendClassWithProperties(alt.WeaponObject, null, WeaponObject); \ No newline at end of file diff --git a/client/src/bindings/js/classes/webView.js b/client/src/bindings/js/classes/webView.js new file mode 100644 index 00000000..7c3a0ac3 --- /dev/null +++ b/client/src/bindings/js/classes/webView.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class WebView { + static get count() { + return alt.WebView.all.length; + } +} + +extendClassWithProperties(alt.WebView, null, WebView); \ No newline at end of file diff --git a/server/src/bindings/Checkpoint.cpp b/server/src/bindings/Checkpoint.cpp index 51cd23e8..cdd55f85 100644 --- a/server/src/bindings/Checkpoint.cpp +++ b/server/src/bindings/Checkpoint.cpp @@ -64,11 +64,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllCheckpoints()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::CHECKPOINT).size()); -} - static void StreamSyncedMetaGetter(js::internal::DynamicPropertyGetterContext& ctx) { if (!ctx.CheckParent()) return; @@ -136,7 +131,6 @@ extern V8Class v8Checkpoint("Checkpoint", v8::Isolate* isolate = v8::Isolate::GetCurrent(); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); V8Helpers::SetAccessor(isolate, tpl, "streamingDistance"); V8Helpers::SetAccessor(isolate, tpl, "visible"); diff --git a/server/src/bindings/Object.cpp b/server/src/bindings/Object.cpp index 8019606b..55854d6d 100644 --- a/server/src/bindings/Object.cpp +++ b/server/src/bindings/Object.cpp @@ -61,11 +61,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllObjects()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::OBJECT).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -92,7 +87,6 @@ extern V8Class v8Object("Object", v8Entity, Constructor, [](v8::Local(isolate, tpl, "activatePhysics"); diff --git a/server/src/bindings/Ped.cpp b/server/src/bindings/Ped.cpp index 956faaf8..69dc4562 100644 --- a/server/src/bindings/Ped.cpp +++ b/server/src/bindings/Ped.cpp @@ -39,11 +39,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllPeds()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::PED).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -70,7 +65,6 @@ extern V8Class v8Ped("Ped", v8Entity, Constructor, [](v8::Local(isolate, tpl, "currentWeapon"); diff --git a/server/src/bindings/Player.cpp b/server/src/bindings/Player.cpp index 6f628cff..c8c4dcb9 100644 --- a/server/src/bindings/Player.cpp +++ b/server/src/bindings/Player.cpp @@ -420,11 +420,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllPlayers()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::PLAYER).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -1454,7 +1449,6 @@ extern V8Class v8Player("Player", V8Helpers::SetStaticMethod(isolate, tpl, "getByID", &StaticGetByID); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetMethod(isolate, tpl, "emit", &Emit); V8Helpers::SetMethod(isolate, tpl, "emitRaw", &EmitRaw); diff --git a/server/src/bindings/Vehicle.cpp b/server/src/bindings/Vehicle.cpp index 13f1ee9a..4c242393 100644 --- a/server/src/bindings/Vehicle.cpp +++ b/server/src/bindings/Vehicle.cpp @@ -71,11 +71,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllVehicles()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::VEHICLE).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -431,7 +426,6 @@ extern V8Class v8Vehicle("Vehicle", V8Helpers::SetStaticMethod(isolate, tpl, "getByID", StaticGetByID); V8Helpers::SetStaticAccessor(isolate, tpl, "all", AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); // Common getter/setters V8Helpers::SetAccessor(isolate, tpl, "destroyed"); diff --git a/server/src/bindings/js/classes/checkpoint.js b/server/src/bindings/js/classes/checkpoint.js index 59c9231a..956ac4c9 100644 --- a/server/src/bindings/js/classes/checkpoint.js +++ b/server/src/bindings/js/classes/checkpoint.js @@ -2,6 +2,9 @@ const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedEntity } = requireBinding("shared/classes/sharedEntity.js"); class Checkpoint { + get count() { + return alt.Checkpoint.all.length; + } setStreamSyncedMeta(key, value) { this.streamSyncedMeta[key] = value; diff --git a/server/src/bindings/js/classes/object.js b/server/src/bindings/js/classes/object.js new file mode 100644 index 00000000..87bfdd06 --- /dev/null +++ b/server/src/bindings/js/classes/object.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Object { + static get count() { + return alt.Object.all.length; + } +} + +extendClassWithProperties(alt.Object, null, Object); \ No newline at end of file diff --git a/server/src/bindings/js/classes/ped.js b/server/src/bindings/js/classes/ped.js new file mode 100644 index 00000000..b40b316b --- /dev/null +++ b/server/src/bindings/js/classes/ped.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Ped { + static get count() { + return alt.Ped.all.length; + } +} + +extendClassWithProperties(alt.Ped, null, Ped); \ No newline at end of file diff --git a/server/src/bindings/js/classes/player.js b/server/src/bindings/js/classes/player.js new file mode 100644 index 00000000..af61d6fe --- /dev/null +++ b/server/src/bindings/js/classes/player.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Player { + static get count() { + return alt.Player.all.length; + } +} + +extendClassWithProperties(alt.Player, null, Player); \ No newline at end of file diff --git a/server/src/bindings/js/classes/vehicle.js b/server/src/bindings/js/classes/vehicle.js new file mode 100644 index 00000000..80006080 --- /dev/null +++ b/server/src/bindings/js/classes/vehicle.js @@ -0,0 +1,9 @@ +const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); + +class Vehicle { + static get count() { + return alt.Vehicle.all.length; + } +} + +extendClassWithProperties(alt.Vehicle, null, Vehicle); \ No newline at end of file diff --git a/shared/bindings/Blip.cpp b/shared/bindings/Blip.cpp index aef4dd2e..deb8e2e1 100644 --- a/shared/bindings/Blip.cpp +++ b/shared/bindings/Blip.cpp @@ -280,11 +280,6 @@ static void AllGetter(v8::Local name, const v8::PropertyCallbackInfo V8_RETURN(resource->GetAllBlips()); } -static void CountGetter(v8::Local name, const v8::PropertyCallbackInfo& info) -{ - V8_RETURN_UINT(alt::ICore::Instance().GetBaseObjects(alt::IBaseObject::Type::BLIP).size()); -} - static void StaticGetByID(const v8::FunctionCallbackInfo& info) { V8_GET_ISOLATE_CONTEXT_RESOURCE(); @@ -403,7 +398,6 @@ extern V8Class v8Blip("Blip", V8Helpers::SetMethod(isolate, tpl, "toString", ToString); V8Helpers::SetStaticAccessor(isolate, tpl, "all", &AllGetter); - V8Helpers::SetStaticAccessor(isolate, tpl, "count", &CountGetter); V8Helpers::SetDynamicProperty(isolate, tpl, "meta", MetaGetter, MetaSetter, MetaDeleter, MetaEnumerator); V8Helpers::SetDynamicProperty(isolate, tpl, "syncedMeta", SyncedMetaGetter, nullptr, nullptr, SyncedMetaEnumerator); diff --git a/client/src/bindings/js/classes/blip.js b/shared/bindings/js/classes/blip.js similarity index 52% rename from client/src/bindings/js/classes/blip.js rename to shared/bindings/js/classes/blip.js index 3b273792..9130c836 100644 --- a/client/src/bindings/js/classes/blip.js +++ b/shared/bindings/js/classes/blip.js @@ -1,4 +1,10 @@ const { extendClassWithProperties } = requireBinding("shared/utils/classes.js"); const { SharedBaseObject } = requireBinding("shared/classes/sharedBaseObject.js"); -extendClassWithProperties(alt.Blip, null, SharedBaseObject); \ No newline at end of file +class Blip { + static get count() { + return alt.Blip.all.length; + } +} + +extendClassWithProperties(alt.Blip, null, Blip, SharedBaseObject); \ No newline at end of file From 710a6453efcfb1dbbcd99139a8ce7ee141d06da5 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 31 Jan 2025 18:45:39 +0100 Subject: [PATCH 20/22] ALTV-659: Improved v8 unhandled error stack trace --- client/src/PromiseRejections.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/PromiseRejections.cpp b/client/src/PromiseRejections.cpp index b1d605ed..05d03099 100644 --- a/client/src/PromiseRejections.cpp +++ b/client/src/PromiseRejections.cpp @@ -33,12 +33,15 @@ void V8Helpers::PromiseRejections::ProcessQueue(CV8ResourceImpl* resource) auto moduleData = resource->GetModuleData(fileName); if(rejection->location.GetLineNumber() != 0 && !moduleData.isBytecode) { - Log::Error << "[V8] Unhandled promise rejection at " << resource->GetResource()->GetName() << ":" << fileName << ":" << rejection->location.GetLineNumber() << " (" - << rejectionMsg << ")" << Log::Endl; + Log::Error << "[V8] Unhandled promise rejection at resource [" << resource->GetResource()->GetName() << "]: " + << "File name: [" << fileName << "], function: [" << rejection->location.GetFuncName() << "], line number:" << rejection->location.GetLineNumber() << ", error: [" + << rejectionMsg << "]" << Log::Endl; } else { - Log::Error << "[V8] Unhandled promise rejection at " << resource->GetResource()->GetName() << ":" << fileName << " (" << rejectionMsg << ")" << Log::Endl; + Log::Error << "[V8] Unhandled promise rejection at resource [" << resource->GetResource()->GetName() << "]: " + << "File name: [" << fileName << "], function: [" << rejection->location.GetFuncName() << "], line number:" << rejection->location.GetLineNumber() << ", error: [" + << rejectionMsg << "]" << Log::Endl; } rejection->stackTrace.Print(1); From 9f8ff7eca3a6cdab223284133b421525acb0b13e Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 31 Jan 2025 18:45:58 +0100 Subject: [PATCH 21/22] ALTV-659: Fixed hash method and RGBA class --- shared/bindings/js/classes/rgba.js | 14 +++++++------- shared/bindings/js/utils.js | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/shared/bindings/js/classes/rgba.js b/shared/bindings/js/classes/rgba.js index 4dcd360f..5abd998f 100644 --- a/shared/bindings/js/classes/rgba.js +++ b/shared/bindings/js/classes/rgba.js @@ -14,18 +14,18 @@ export class RGBA { if (args.length === 4) this.a = args[3]; } else if (args.length === 1) { const arg = args[0]; - if (typeof arg === "object") { - const obj = arg; - this.r = obj.r; - this.g = obj.g; - this.b = obj.b; - if (obj.a) this.a = obj.a; - } else if (Array.isArray(arg)) { + if (Array.isArray(arg)) { const arr = arg; this.r = arr[0]; this.g = arr[1]; this.b = arr[2]; if (arr.length === 4) this.a = arr[3]; + } else if (typeof arg === "object") { + const obj = arg; + this.r = obj.r; + this.g = obj.g; + this.b = obj.b; + if (obj.a) this.a = obj.a; } } else throw new Error("Invalid arguments"); } diff --git a/shared/bindings/js/utils.js b/shared/bindings/js/utils.js index de63609a..6b4db93b 100644 --- a/shared/bindings/js/utils.js +++ b/shared/bindings/js/utils.js @@ -827,9 +827,10 @@ export function isObject(value) { } export function hash(str) { - assertIsType(str, "string", "Expected a string as first argument"); + // TODO: Return when stack trace will be done + // assertIsType(str, "string", "Expected a string as first argument"); - const string = str.toLowerCase(); + const string = str.toString().toLowerCase(); const length = string.length; let hash = 0; for (let i = 0; i < length; i++) { From a3097a85a7e27bec34494b13e37a443f5aefd609 Mon Sep 17 00:00:00 2001 From: Potapenko Date: Fri, 7 Feb 2025 12:01:48 +0100 Subject: [PATCH 22/22] ALTV-712: Added pragma once to generated bindings --- tools/convert-bindings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/convert-bindings.js b/tools/convert-bindings.js index ca2207ee..5945bb38 100644 --- a/tools/convert-bindings.js +++ b/tools/convert-bindings.js @@ -24,6 +24,7 @@ const paths = [ // Full output file const resultTemplate = `// !!! THIS FILE WAS AUTOMATICALLY GENERATED (ON {DATE}), DO NOT EDIT MANUALLY !!! +#pragma once #include "helpers/JSBindings.h" namespace js {