diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6c44c3a --- /dev/null +++ b/.clang-format @@ -0,0 +1,86 @@ +# C++ files +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakInheritanceList: BeforeComma +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: true +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWrappedFunctionNames: true +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: Inner +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never diff --git a/binding.gyp b/binding.gyp index 026a266..916cff2 100644 --- a/binding.gyp +++ b/binding.gyp @@ -8,13 +8,13 @@ 'src/NetworkListener.cpp', 'src/nuclear/src/extension/network/NUClearNetwork.cpp', 'src/nuclear/src/util/platform.cpp', - 'src/nuclear/src/util/serialise/xxhash.c', - 'src/nuclear/src/util/network/get_interfaces.cpp' + 'src/nuclear/src/util/network/get_interfaces.cpp', + 'src/nuclear/src/util/network/resolve.cpp', + 'src/nuclear/src/util/serialise/xxhash.cpp' ], 'cflags': [], 'include_dirs': [ '=10.20 || >=12.17" + } + }, + "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { + "dependencies": { "file-uri-to-path": "1.0.0" } }, - "dequal": { + "node_modules/dequal": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "diff": { + "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.3.1" + } }, - "file-uri-to-path": { + "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, - "kleur": { + "node_modules/kleur": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "mri": { + "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "napi-thread-safe-callback": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/napi-thread-safe-callback/-/napi-thread-safe-callback-0.0.6.tgz", - "integrity": "sha512-X7uHCOCdY4u0yamDxDrv3jF2NtYc8A1nvPzBQgvpoSX+WB3jAe2cVNsY448V1ucq7Whf9Wdy02HEUoLW5rJKWg==" + "dev": true, + "engines": { + "node": ">=4" + } }, - "node-addon-api": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", - "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==" + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" }, - "sade": { + "node_modules/sade": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", "dev": true, - "requires": { + "dependencies": { "mri": "^1.1.0" + }, + "engines": { + "node": ">= 6" } }, - "totalist": { + "node_modules/totalist": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "uvu": { + "node_modules/uvu": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.2.tgz", "integrity": "sha512-m2hLe7I2eROhh+tm3WE5cTo/Cv3WQA7Oc9f7JB6uWv+/zVKvfAm53bMyOoGOSZeQ7Ov2Fu9pLhFr7p07bnT20w==", "dev": true, - "requires": { + "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3", "totalist": "^2.0.0" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" } } } diff --git a/package.json b/package.json index 890883d..b5abc6f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ }, "dependencies": { "bindings": "^1.5.0", - "napi-thread-safe-callback": "0.0.6", - "node-addon-api": "^4.2.0" + "node-addon-api": "^7.0.0" } } diff --git a/src/NetworkBinding.cpp b/src/NetworkBinding.cpp index 66bc608..6dd88a9 100644 --- a/src/NetworkBinding.cpp +++ b/src/NetworkBinding.cpp @@ -16,6 +16,7 @@ */ #include "NetworkBinding.hpp" + #include "NetworkListener.hpp" #include "nuclear/src/util/serialise/xxhash.hpp" @@ -24,7 +25,7 @@ namespace NUClear { using extension::network::NUClearNetwork; using util::serialise::xxhash64; -NetworkBinding::NetworkBinding(const Napi::CallbackInfo& info): Napi::ObjectWrap(info) {} +NetworkBinding::NetworkBinding(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} Napi::Value NetworkBinding::Hash(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); @@ -35,8 +36,7 @@ Napi::Value NetworkBinding::Hash(const Napi::CallbackInfo& info) { uint64_t hash = xxhash64(s.c_str(), s.size(), 0x4e55436c); // Return hash - return - Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)).As(); + return Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)).As(); } else { Napi::TypeError::New(env, "Invalid input for hash(): expected a string").ThrowAsJavaScriptException(); @@ -52,9 +52,9 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { return; } - const Napi::Value& arg_hash = info[0]; - const Napi::Value& arg_payload = info[1]; - const Napi::Value& arg_target = info[2]; + const Napi::Value& arg_hash = info[0]; + const Napi::Value& arg_payload = info[1]; + const Napi::Value& arg_target = info[2]; const Napi::Value& arg_reliable = info[3]; uint64_t hash = 0; @@ -65,8 +65,10 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { // Read reliability information if (arg_reliable.IsBoolean()) { reliable = arg_reliable.As().Value(); - } else { - Napi::TypeError::New(env, "Invalid `reliable` option for send(): expected a boolean").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New(env, "Invalid `reliable` option for send(): expected a boolean") + .ThrowAsJavaScriptException(); return; } @@ -76,23 +78,27 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { } // Otherwise, we accept null and undefined to mean everybody else if (!arg_target.IsUndefined() && !arg_target.IsNull()) { - Napi::TypeError::New(env, "Invalid `target` option for send(): expected a string (for targeted), or null/undefined (for untargeted)").ThrowAsJavaScriptException(); + Napi::TypeError::New( + env, + "Invalid `target` option for send(): expected a string (for targeted), or null/undefined (for untargeted)") + .ThrowAsJavaScriptException(); return; } // Read the data information if (arg_payload.IsTypedArray()) { Napi::TypedArray typed_array = arg_payload.As(); - Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); + Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); - char* data = reinterpret_cast(buffer.Data()); + char* data = reinterpret_cast(buffer.Data()); char* start = data + typed_array.ByteOffset(); - char* end = start + typed_array.ByteLength(); + char* end = start + typed_array.ByteLength(); payload.insert(payload.begin(), start, end); } else { - Napi::TypeError::New(env, "Invalid `payload` option for send(): expected a Buffer").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Invalid `payload` option for send(): expected a Buffer") + .ThrowAsJavaScriptException(); return; } @@ -104,21 +110,24 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { // Otherwise try to interpret it as a buffer that contains the hash else if (arg_hash.IsTypedArray()) { Napi::TypedArray typed_array = arg_hash.As(); - Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); + Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); - uint8_t* data = reinterpret_cast(buffer.Data()); + uint8_t* data = reinterpret_cast(buffer.Data()); uint8_t* start = data + typed_array.ByteOffset(); - uint8_t* end = start + typed_array.ByteLength(); + uint8_t* end = start + typed_array.ByteLength(); if (std::distance(start, end) == 8) { std::memcpy(&hash, start, 8); } else { - Napi::TypeError::New(env, "Invalid `hash` option for send(): provided Buffer length is not 8").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Invalid `hash` option for send(): provided Buffer length is not 8") + .ThrowAsJavaScriptException(); return; } - } else { - Napi::TypeError::New(env, "Invalid `hash` option for send(): expected a string or Buffer").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New(env, "Invalid `hash` option for send(): expected a string or Buffer") + .ThrowAsJavaScriptException(); return; } @@ -131,130 +140,88 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { } } -void NetworkBinding::On(const Napi::CallbackInfo& info) { +void NetworkBinding::OnPacket(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - if (info[0].IsString() && info[1].IsFunction()) { - std::string event = info[0].As().Utf8Value(); - - // `ThreadSafeCallback` is from the napi-thread-safe-callback npm package. It allows for running a JS callback - // from a thread that isn't the main addon thread (like where the NUClearNet packet callbacks are called from). - // Similar thread-safe callback functionality has been added to NAPI natively, but it's still experimental at - // the time of writing. - auto cb = std::make_shared(info[1].As()); - - if (event == "packet") { - this->net.set_packet_callback([cb = std::move(cb)]( - const NUClearNetwork::NetworkTarget& t, const uint64_t& hash, const bool& reliable, std::vector&& payload) { - - std::string name = t.name; - std::string address; - uint16_t port = 0; - - // Extract the IP address and port - char c[255]; - std::memset(c, 0, sizeof(c)); - switch (t.target.sock.sa_family) { - case AF_INET: - inet_ntop(t.target.sock.sa_family, const_cast(&t.target.ipv4.sin_addr), c, sizeof(c)); - port = ntohs(t.target.ipv4.sin_port); - break; - - case AF_INET6: - inet_ntop( - t.target.sock.sa_family, const_cast(&t.target.ipv6.sin6_addr), c, sizeof(c)); - port = ntohs(t.target.ipv6.sin6_port); - break; - } - address = c; - - cb->call([name, address, port, reliable, hash, payload](Napi::Env env, std::vector &args) { - args = { - Napi::String::New(env, name), - Napi::String::New(env, address), - Napi::Number::New(env, port), - Napi::Boolean::New(env, reliable), - Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)), - Napi::Buffer::Copy(env, payload.data(), payload.size()) - }; - }); + // Function to execute on the network thread + on_packet = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnPacket", 0, 1); + + this->net.set_packet_callback([this](const NUClearNetwork::NetworkTarget& t, + const uint64_t& hash, + const bool& reliable, + std::vector&& payload) { + on_packet.BlockingCall([&t, hash, reliable, p = std::move(payload)](Napi::Env env, Napi::Function js_callback) { + auto addr = t.target.address(); + js_callback.Call({ + Napi::String::New(env, t.name), + Napi::String::New(env, addr.first), + Napi::Number::New(env, addr.second), + Napi::Boolean::New(env, reliable), + Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)), + Napi::Buffer::Copy(env, p.data(), p.size()), }); - } - else if (event == "join" || event == "leave") { - auto f = [cb = std::move(cb)](const NUClearNetwork::NetworkTarget& t) { - std::string name = t.name; - std::string address; - uint16_t port = 0; - - // Extract the IP address and port - char c[255]; - std::memset(c, 0, sizeof(c)); - switch (t.target.sock.sa_family) { - case AF_INET: - inet_ntop(t.target.sock.sa_family, const_cast(&t.target.ipv4.sin_addr), c, sizeof(c)); - port = ntohs(t.target.ipv4.sin_port); - break; - - case AF_INET6: - inet_ntop( - t.target.sock.sa_family, const_cast(&t.target.ipv6.sin6_addr), c, sizeof(c)); - port = ntohs(t.target.ipv6.sin6_port); - break; - - default: - // The system has a corrupted network peer record, but we can't throw to JS from here, since we - // don't have an env object. cb->callError(string) is available from the - // napi-thread-safe-callback library, but that requires changing the callback signature on the - // JS side to accept a potential error as the first argument. This would be a breaking change, - // but we can do it if deemed necessary, and update all users of nuclearnet.js. - return; - } - address = c; - - cb->call([name, address, port](Napi::Env env, std::vector &args) { - args = { - Napi::String::New(env, name), - Napi::String::New(env, address), - Napi::Number::New(env, port), - }; - }); - }; - - if (event == "join") { - this->net.set_join_callback(std::move(f)); - } - else { - this->net.set_leave_callback(std::move(f)); - } - } - else if (event == "wait") { - this->net.set_next_event_callback([cb = std::move(cb)](std::chrono::steady_clock::time_point t) { - using namespace std::chrono; - int ms = duration_cast>(t - steady_clock::now()).count(); - ms++; // Add 1 to account for any funky rounding - - cb->call([ms](Napi::Env env, std::vector &args) { - args = {Napi::Number::New(env, ms)}; - }); + }); + }); +} + +void NetworkBinding::OnJoin(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Function to execute on the network thread + on_join = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnJoin", 0, 1); + + this->net.set_join_callback([this](const NUClearNetwork::NetworkTarget& t) { + on_join.BlockingCall([&t](Napi::Env env, Napi::Function js_callback) { + auto addr = t.target.address(); + js_callback.Call({ + Napi::String::New(env, t.name), + Napi::String::New(env, addr.first), + Napi::Number::New(env, addr.second), }); - } - else { - Napi::TypeError::New(env, "Invalid `eventName` argument for on(): expected one of 'packet', 'join', 'leave', or 'wait'").ThrowAsJavaScriptException(); - return; - } - } - else { - Napi::TypeError::New(env, "Invalid arguments for on(): expected an event name (string) and a callback (function)").ThrowAsJavaScriptException(); - } + }); + }); +} + +void NetworkBinding::OnLeave(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Function to execute on the network thread + on_leave = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnLeave", 0, 1); + + this->net.set_leave_callback([this](const NUClearNetwork::NetworkTarget& t) { + on_leave.BlockingCall([&t](Napi::Env env, Napi::Function js_callback) { + auto addr = t.target.address(); + js_callback.Call({ + Napi::String::New(env, t.name), + Napi::String::New(env, addr.first), + Napi::Number::New(env, addr.second), + }); + }); + }); +} + +void NetworkBinding::OnWait(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Function to execute on the network thread + on_wait = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnWait", 0, 1); + + this->net.set_next_event_callback([this](const std::chrono::steady_clock::time_point& t) { + using namespace std::chrono; + // Add 1 to account for any funky rounding + int ms = 1 + duration_cast>(t - steady_clock::now()).count(); + on_wait.BlockingCall( + [ms](Napi::Env env, Napi::Function js_callback) { js_callback.Call({Napi::Number::New(env, ms)}); }); + }); } void NetworkBinding::Reset(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - const Napi::Value& arg_name = info[0]; + const Napi::Value& arg_name = info[0]; const Napi::Value& arg_group = info[1]; - const Napi::Value& arg_port = info[2]; - const Napi::Value& arg_mtu = info[3]; + const Napi::Value& arg_port = info[2]; + const Napi::Value& arg_mtu = info[3]; std::string name = ""; std::string group = "239.226.152.162"; @@ -266,7 +233,8 @@ void NetworkBinding::Reset(const Napi::CallbackInfo& info) { group = arg_group.As().Utf8Value(); } else { - Napi::TypeError::New(env, "Invalid `group` option for reset(): multicast group must be a string").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Invalid `group` option for reset(): multicast group must be a string") + .ThrowAsJavaScriptException(); return; } @@ -334,24 +302,55 @@ void NetworkBinding::Destroy(const Napi::CallbackInfo& info) { WSASetEvent(this->listenerNotifier); #endif - // Replace the ThreadSafeCallback instances to clean up the extra threads they created - this->net.set_packet_callback([](const NUClearNetwork::NetworkTarget& t, const uint64_t& hash, const bool& reliable, std::vector&& payload) {}); + // Create empty lambdas for the callbacks, to prevent them from being called + this->net.set_packet_callback([](const NUClearNetwork::NetworkTarget& t, + const uint64_t& hash, + const bool& reliable, + std::vector&& payload) {}); this->net.set_join_callback([](const NUClearNetwork::NetworkTarget& t) {}); this->net.set_leave_callback([](const NUClearNetwork::NetworkTarget& t) {}); this->net.set_next_event_callback([](std::chrono::steady_clock::time_point t) {}); + + // Release the thread safe functions + on_packet.Release(); + on_join.Release(); + on_leave.Release(); + on_wait.Release(); } void NetworkBinding::Init(Napi::Env env, Napi::Object exports) { - Napi::Function func = - DefineClass(env, - "NetworkBinding", - {InstanceMethod<&NetworkBinding::Send>("send", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::On>("on", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Reset>("reset", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Process>("process", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Shutdown>("shutdown", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Hash>("hash", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Destroy>("destroy", static_cast(napi_writable | napi_configurable))}); + Napi::Function func = DefineClass(env, + "NetworkBinding", + {InstanceMethod<&NetworkBinding::Send>( + "send", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnPacket>( + "onPacket", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnJoin>( + "onJoin", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnLeave>( + "onLeave", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnWait>( + "onWait", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Reset>( + "reset", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Process>( + "process", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Shutdown>( + "shutdown", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Hash>( + "hash", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Destroy>( + "destroy", + static_cast(napi_writable | napi_configurable))}); Napi::FunctionReference* constructor = new Napi::FunctionReference(); diff --git a/src/NetworkBinding.hpp b/src/NetworkBinding.hpp index 8b36da0..4d1340a 100644 --- a/src/NetworkBinding.hpp +++ b/src/NetworkBinding.hpp @@ -18,10 +18,10 @@ #ifndef NETWORKBINDING_H #define NETWORKBINDING_H -#include "nuclear/src/extension/network/NUClearNetwork.hpp" -#include "napi-thread-safe-callback.hpp" #include +#include "nuclear/src/extension/network/NUClearNetwork.hpp" + namespace NUClear { class NetworkBinding : public Napi::ObjectWrap { @@ -30,7 +30,10 @@ class NetworkBinding : public Napi::ObjectWrap { Napi::Value Hash(const Napi::CallbackInfo& info); void Send(const Napi::CallbackInfo& info); - void On(const Napi::CallbackInfo& info); + void OnPacket(const Napi::CallbackInfo& info); + void OnJoin(const Napi::CallbackInfo& info); + void OnLeave(const Napi::CallbackInfo& info); + void OnWait(const Napi::CallbackInfo& info); void Reset(const Napi::CallbackInfo& info); void Process(const Napi::CallbackInfo& info); void Shutdown(const Napi::CallbackInfo& info); @@ -38,6 +41,10 @@ class NetworkBinding : public Napi::ObjectWrap { extension::network::NUClearNetwork net; bool destroyed = false; + Napi::ThreadSafeFunction on_packet; + Napi::ThreadSafeFunction on_join; + Napi::ThreadSafeFunction on_leave; + Napi::ThreadSafeFunction on_wait; #ifdef _WIN32 WSAEVENT listenerNotifier; diff --git a/src/NetworkListener.cpp b/src/NetworkListener.cpp index f6be013..230410c 100644 --- a/src/NetworkListener.cpp +++ b/src/NetworkListener.cpp @@ -21,7 +21,7 @@ namespace NUClear { NetworkListener::NetworkListener(Napi::Env& env, NetworkBinding* binding) -: Napi::AsyncProgressWorker(env), binding(binding) { + : Napi::AsyncProgressWorker(env), binding(binding) { std::vector notifyfds = this->binding->net.listen_fds(); #ifdef _WIN32 @@ -29,9 +29,7 @@ NetworkListener::NetworkListener(Napi::Env& env, NetworkBinding* binding) for (auto& fd : notifyfds) { auto event = WSACreateEvent(); if (event == WSA_INVALID_EVENT) { - throw std::system_error(WSAGetLastError(), - std::system_category(), - "WSACreateEvent() for notify fd failed"); + throw std::system_error(WSAGetLastError(), std::system_category(), "WSACreateEvent() for notify fd failed"); } if (WSAEventSelect(fd, event, FD_READ | FD_CLOSE) == SOCKET_ERROR) { @@ -61,7 +59,8 @@ NetworkListener::~NetworkListener() { #ifdef _WIN32 for (auto& event : this->events) { if (!WSACloseEvent(event)) { - std::cerr << "[NUClearNet.js NetworkListener] WSACloseEvent() failed, error code " << WSAGetLastError() << std::endl; + std::cerr << "[NUClearNet.js NetworkListener] WSACloseEvent() failed, error code " << WSAGetLastError() + << std::endl; } } #endif @@ -76,28 +75,30 @@ void NetworkListener::Execute(const Napi::AsyncProgressWorker::ExecutionPr #ifdef _WIN32 // Wait for events and check for shutdown - auto event_index = WSAWaitForMultipleEvents(this->events.size(), this->events.data(), false, WSA_INFINITE, false); + auto event_index = + WSAWaitForMultipleEvents(this->events.size(), this->events.data(), false, WSA_INFINITE, false); // Check if the return value is an event in our list if (event_index >= WSA_WAIT_EVENT_0 && event_index < WSA_WAIT_EVENT_0 + this->events.size()) { // Get the signalled event - auto& event = this->events[event_index - WSA_WAIT_EVENT_0]; + auto& event = this->events[event_index - WSA_WAIT_EVENT_0]; if (event == this->notifier) { // Reset the notifier signal if (!WSAResetEvent(event)) { - throw std::system_error( - WSAGetLastError(), std::system_category(), "WSAResetEvent() for notifier failed"); + throw std::system_error(WSAGetLastError(), + std::system_category(), + "WSAResetEvent() for notifier failed"); } - } else { + } + else { // Get the corresponding fd for the event auto& fd = this->fds[event_index - WSA_WAIT_EVENT_0]; // Enumumerate the socket events to work out which ones fired WSANETWORKEVENTS wsne; if (WSAEnumNetworkEvents(fd, event, &wsne) == SOCKET_ERROR) { - throw std::system_error( - WSAGetLastError(), std::system_category(), "WSAEnumNetworkEvents() failed"); + throw std::system_error(WSAGetLastError(), std::system_category(), "WSAEnumNetworkEvents() failed"); } // Exit the run loop if the fd was closed @@ -128,16 +129,20 @@ void NetworkListener::Execute(const Napi::AsyncProgressWorker::ExecutionPr // Notify the system something happened if we're running and have data to read. // Will trigger OnProgress() below to read the data. if (run && data) { - // Should really be `p.Signal()` here, but it has a bug at the moment - // See https://github.com/nodejs/node-addon-api/issues/1081 - p.Send(nullptr, 0); + p.Signal(); } } } void NetworkListener::OnProgress(const char*, size_t) { // If we're here in OnProgress(), then there's data to process - this->binding->net.process(); + try { + this->binding->net.process(); + } + catch (const std::exception& e) { + // We can't throw to javascript as we are in another thread + // Still we don't want to crash the process so swallow the exception + } } void NetworkListener::OnOK() {} diff --git a/src/NetworkListener.hpp b/src/NetworkListener.hpp index c5c5d4e..5f22208 100644 --- a/src/NetworkListener.hpp +++ b/src/NetworkListener.hpp @@ -18,9 +18,10 @@ #ifndef NETWORKLISTENER_H #define NETWORKLISTENER_H -#include "NetworkBinding.hpp" #include +#include "NetworkBinding.hpp" + namespace NUClear { class NetworkListener : public Napi::AsyncProgressWorker {