From cf59c854a1894429a5af8a669a39c62d24d3b667 Mon Sep 17 00:00:00 2001 From: "M. Samil Atesoglu" Date: Wed, 1 Jul 2026 19:08:07 +0300 Subject: [PATCH] Merge ScalarArithmetic node into Arithmetic node & add set type menu items --- Plugins/nosReflect/Nodes/Arithmetic.nosnode | 20 +- .../nosReflect/Nodes/ScalarArithmetic.nosnode | 121 ---- Plugins/nosReflect/Source/Arithmetic.cpp | 620 +++++++++--------- Plugins/nosReflect/Source/PluginMain.cpp | 16 +- 4 files changed, 329 insertions(+), 448 deletions(-) delete mode 100644 Plugins/nosReflect/Nodes/ScalarArithmetic.nosnode diff --git a/Plugins/nosReflect/Nodes/Arithmetic.nosnode b/Plugins/nosReflect/Nodes/Arithmetic.nosnode index b19700b4..b787770b 100644 --- a/Plugins/nosReflect/Nodes/Arithmetic.nosnode +++ b/Plugins/nosReflect/Nodes/Arithmetic.nosnode @@ -2,7 +2,18 @@ "nodes": [ { "class_name": "Arithmetic", - "hide_in_context_menu": true, + "menu_info": { + "category": "Math", + "display_name": "Arithmetic", + "name_aliases": [ + "math", + "operator", + "add", + "sub", + "mul", + "div" + ] + }, "presets": [ { "menu_info": { @@ -116,13 +127,6 @@ } ] } - }, - { - "class_name": "ArithmeticDynamic", - "hide_in_context_menu": true, - "node": { - "contents_type": "Job" - } } ], "schema_version": "1.4-v1" diff --git a/Plugins/nosReflect/Nodes/ScalarArithmetic.nosnode b/Plugins/nosReflect/Nodes/ScalarArithmetic.nosnode deleted file mode 100644 index b2454856..00000000 --- a/Plugins/nosReflect/Nodes/ScalarArithmetic.nosnode +++ /dev/null @@ -1,121 +0,0 @@ -{ - "nodes": [ - { - "class_name": "ScalarArithmetic", - "hide_in_context_menu": true, - "presets": [ - { - "menu_info": { - "display_name": "Scalar Add", - "category": "Arithmetic", - "name_aliases": [ - "plus", - "sum" - ] - }, - "params": [ - { - "name": "Operator", - "type_name": "nos.reflect.BinaryOperator", - "value": "ADD" - } - ] - }, - { - "menu_info": { - "display_name": "Scalar Sub", - "category": "Arithmetic", - "name_aliases": [ - "minus", - "difference" - ] - }, - "params": [ - { - "name": "Operator", - "type_name": "nos.reflect.BinaryOperator", - "value": "SUB" - } - ] - }, - { - "menu_info": { - "display_name": "Scalar Mul", - "category": "Arithmetic", - "name_aliases": [ - "times", - "product" - ] - }, - "params": [ - { - "name": "Operator", - "type_name": "nos.reflect.BinaryOperator", - "value": "MUL" - } - ] - }, - { - "menu_info": { - "display_name": "Scalar Div", - "category": "Arithmetic", - "name_aliases": [ - "divide", - "quotient" - ] - }, - "params": [ - { - "name": "Operator", - "type_name": "nos.reflect.BinaryOperator", - "value": "DIV" - } - ] - }, - { - "menu_info": { - "display_name": "Scalar Log", - "category": "Arithmetic", - "name_aliases": [ - "logarithm", - "ln" - ] - }, - "params": [ - { - "name": "Operator", - "type_name": "nos.reflect.BinaryOperator", - "value": "LOG" - } - ] - } - ], - "node": { - "contents_type": "Job", - "pins": [ - { - "name": "Output", - "type_name": "nos.Generic", - "show_as": "OUTPUT_PIN", - "can_show_as": "OUTPUT_PIN_ONLY", - "connection_conditions": "!resource" - }, - { - "name": "A", - "type_name": "nos.Generic", - "show_as": "INPUT_PIN", - "can_show_as": "INPUT_PIN_OR_PROPERTY", - "connection_conditions": "!resource" - }, - { - "name": "Scalar", - "type_name": "nos.Generic", - "show_as": "INPUT_PIN", - "can_show_as": "INPUT_PIN_OR_PROPERTY" - } - ] - } - } - ], - "schema_version": "1.4-v1" -} diff --git a/Plugins/nosReflect/Source/Arithmetic.cpp b/Plugins/nosReflect/Source/Arithmetic.cpp index cb3074c8..2bceb88c 100644 --- a/Plugins/nosReflect/Source/Arithmetic.cpp +++ b/Plugins/nosReflect/Source/Arithmetic.cpp @@ -3,6 +3,9 @@ #include "TypeCommon.h" #include "nosReflect/Reflect_generated.h" +#include +#include + namespace nos::reflect { NOS_REGISTER_NAME(Type) @@ -26,7 +29,12 @@ std::string ToLower(const char* str) { std::string s(str); std::transform(s.begin(), s.end(), s.begin(), ::tolower); - return s; + return s; +} + +static bool IsScalarBaseType(nosTypeInfo const& ty) +{ + return ty.BaseType == NOS_BASE_TYPE_INT || ty.BaseType == NOS_BASE_TYPE_UINT || ty.BaseType == NOS_BASE_TYPE_FLOAT; } static void MapScalarToT(nosTypeInfo ty, auto&& f) @@ -86,14 +94,14 @@ static void DoOp(reflect::BinaryOperator op, nosTypeInfo const& ty, const uint8_ return; } - MapScalarToT(ty, [&]() -> void { + MapScalarToT(ty, [&]() -> void { using RightHandSideT = std::conditional_t; switch(op) { case reflect::BinaryOperator::ADD: *(T*)dst = *(const T*)lhs + *(RightHandSideT*)rhs; break; case reflect::BinaryOperator::SUB: *(T*)dst = *(const T*)lhs - *(RightHandSideT*)rhs; break; case reflect::BinaryOperator::MUL: *(T*)dst = *(const T*)lhs * *(RightHandSideT*)rhs; break; - case reflect::BinaryOperator::DIV: + case reflect::BinaryOperator::DIV: { if (T(0) != *(RightHandSideT*)rhs) *(T*)dst = *(T*)lhs / *(RightHandSideT*)rhs; @@ -124,64 +132,45 @@ const char* BinaryOpToDisplayName(reflect::BinaryOperator op) return names[op].c_str(); } -template -struct ArithmeticNodeContext : NodeContext +// Arithmetic: computes A (op) B. A and Output share one type; B either matches A (elementwise) or is a +// scalar that broadcasts across A's fields. The type flows through the pins - there is no manual pin +// (re)creation. The operator is chosen from the node's context menu (or baked in by a preset) and persists +// as a template parameter. Scalar-broadcast and per-type math (formerly ScalarArithmetic / ArithmeticDynamic +// and the legacy nos.math.* nodes) are all folded into this one node; see MigrateNode. +struct ArithmeticNode : NodeContext { - std::optional Type = std::nullopt; - std::optional Operator; - std::conditional_t, std::tuple<>> ScalarType = - []() { - if constexpr (IsScalarArithmetic) - return std::nullopt; - else - return std::tuple<>{}; - }(); + std::optional Type = std::nullopt; // type of A / Output + std::optional Operator = std::nullopt; - nosResult OnCreate(const fb::Node* node) override + nosResult OnCreate(nosFbNodePtr node) override { - std::optional newTypeName; - + std::optional typeFromTemplate; if (flatbuffers::IsFieldPresent(node, fb::Node::VT_TEMPLATE_PARAMETERS)) { - for (auto templateParam : *node->template_parameters()) + for (auto* tp : *node->template_parameters()) { - if ("string" == templateParam->name()->string_view() || - NSN_Type == templateParam->name()->string_view()) - { - newTypeName = nos::Name((const char*)(templateParam->value()->Data())); - continue; - } - if ("nos.reflect.BinaryOperator" == templateParam->name()->string_view() || - NSN_Operator == templateParam->name()->string_view()) - { - Operator = *InterpretObjectData((const void*)templateParam->value()->Data()); - continue; - } + auto name = tp->name()->string_view(); + if ("string" == name || NSN_Type == name) + typeFromTemplate = nos::Name((const char*)tp->value()->Data()); + else if ("nos.reflect.BinaryOperator" == name || NSN_Operator == name) + Operator = *InterpretObjectData((const void*)tp->value()->Data()); } } - if (newTypeName && *newTypeName != NSN_TypeNameGeneric) { - Type = nos::TypeInfo(*newTypeName); - if (node->class_name()->str() == "nos.reflect." + NSN_ArithmeticDynamic.AsString()) { - if (node->pins() && node->pins()->size()) - ResolvePinsForArithmeticDynamic(node); - else - CreatePins(); - } - } - if constexpr (IsScalarArithmetic) - { - std::optional scalarTypeName; - for (auto* pin : *node->pins()) - { - if (pin->name()->string_view() == NSN_Scalar) - { - scalarTypeName = nos::Name(pin->type_name()->str()); - break; - } - } - if (scalarTypeName && *scalarTypeName != NSN_TypeNameGeneric) - ScalarType = nos::TypeInfo(*scalarTypeName); + // A preset supplies the type via a template parameter; a saved/generic node carries it on the pins. + nos::Name typeName = typeFromTemplate.value_or(NSN_TypeNameGeneric); + if (typeName == NSN_TypeNameGeneric) + if (auto* a = GetPin(NSN_A)) + typeName = a->TypeName; + + if (typeName != NSN_TypeNameGeneric && nos::TypeInfo(typeName)) + { + // Give still-generic pins the concrete type (typed presets start generic). Pins that already + // carry a type are left as-is, so a migrated node's scalar B pin keeps driving broadcast. + for (nos::Name pinName : {nos::Name(NSN_A), nos::Name(NSN_B), nos::Name(NSN_Output)}) + if (auto* pin = GetPin(pinName); pin && pin->TypeName == NSN_TypeNameGeneric) + SetPinType(pinName, typeName); + OnTypeUpdated(typeName); } if (Operator) @@ -189,294 +178,331 @@ struct ArithmeticNodeContext : NodeContext return NOS_RESULT_SUCCESS; } - void ResolvePinsForArithmeticDynamic(const fb::Node* node) { - flatbuffers::FlatBufferBuilder fbb; - std::vector<::flatbuffers::Offset> pinsToUpdate = {}; - for (auto pin : *node->pins()) { - pinsToUpdate.push_back(CreatePartialPinUpdateDirect(fbb, pin->id(), nullptr, nos::fb::CreatePinOrphanStateDirect(fbb))); + // Types this node can operate on: reject arrays, unions, table (byte-size-less) structs and resources. + static bool IsTypeSupported(nos::TypeInfo& info) + { + if (info->BaseType == NOS_BASE_TYPE_STRUCT && !info->ByteSize) + return false; + if (info->BaseType == NOS_BASE_TYPE_ARRAY || info->BaseType == NOS_BASE_TYPE_UNION) + return false; + for (int i = 0; i < info->AttributeCount; ++i) + if (info->Attributes[i].Name == NOS_NAME_STATIC("resource")) + return false; + return true; + } + + // Types worth offering in the type menus / presets: supported scalars, strings, and builtin structs + // (vecN etc.) - not raw tables, enums or engine-internal types. + static bool IsArithmeticType(nos::TypeInfo& info) + { + if (info->BaseType == NOS_BASE_TYPE_NONE || IsEnumType(info) || !IsTypeSupported(info)) + return false; + if (info->BaseType == NOS_BASE_TYPE_STRUCT) + { + bool builtin = false; + for (int i = 0; i < info->AttributeCount; ++i) + if (info->Attributes[i].Name == NOS_NAME_STATIC("builtin")) + builtin = true; + if (!builtin) + return false; } - HandleEvent(CreateAppEvent(fbb, - CreatePartialNodeUpdateDirect(fbb, - &NodeId, - ClearFlags::NONE, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - &pinsToUpdate))); + return true; + } + // Output takes A's type (A defines the result shape for both elementwise and broadcast); until A is + // known it previews B's type. + static nos::Name DeriveOutputType(nos::Name aType, nos::Name bType) + { + return aType != NSN_TypeNameGeneric ? aType : bType; } - void CreatePins() { - auto& type = *Type; - auto defBuf = GetDefaultValueOfType(type->TypeName); // TODO: This can be freed after type is unloaded, so beware. - if (!defBuf) - return; - nos::Buffer buf = defBuf->GetBuffer(); - std::vector data = buf; - flatbuffers::FlatBufferBuilder fbb; - std::vector> pinsToAdd = {}; - - std::vector pinNames = { NOS_NAME("A"), NOS_NAME("B"), NOS_NAME("Output")}; - std::vector pinShowAs = { nos::fb::ShowAs::INPUT_PIN, nos::fb::ShowAs::INPUT_PIN, nos::fb::ShowAs::OUTPUT_PIN }; - std::vector pinCanShowAs = { nos::fb::CanShowAs::INPUT_PIN_OR_PROPERTY, nos::fb::CanShowAs::INPUT_PIN_OR_PROPERTY, nos::fb::CanShowAs::OUTPUT_PIN_ONLY }; - for (uint32_t pinIndx = 0; pinIndx < pinNames.size(); pinIndx++) { - nos::fb::TPin pin{}; - uuid id = nosEngine.GenerateID(); - pin.id = id; - pin.name = nos::Name(pinNames[pinIndx]).AsCStr(); - pin.type_name = nos::Name(Type->TypeName).AsCStr(); - pin.show_as = pinShowAs[pinIndx]; - pin.can_show_as = pinCanShowAs[pinIndx]; - pin.data = data; - pin.display_name = nos::Name(pinNames[pinIndx]).AsCStr(); - pinsToAdd.push_back(fb::CreatePin(fbb, &pin)); + nosResult OnResolvePinDataTypes(nosResolvePinDataTypesParams* params) override + { + nos::Name instigator(params->InstigatorPinName); + nos::TypeInfo incoming(params->IncomingTypeName); + if (!IsTypeSupported(incoming)) + { + strcpy(params->OutErrorMessage, "Type not supported for arithmetic."); + return NOS_RESULT_FAILED; + } + auto* aPin = GetPin(NSN_A); + auto* bPin = GetPin(NSN_B); + nos::Name curA = aPin ? aPin->TypeName : nos::Name(NSN_TypeNameGeneric); + nos::Name curB = bPin ? bPin->TypeName : nos::Name(NSN_TypeNameGeneric); + + // A connection to B that neither matches A nor is a scalar cannot be combined. + if (instigator == NSN_B && curA != NSN_TypeNameGeneric && + curA != nos::Name(incoming->TypeName) && !IsScalarBaseType(*incoming)) + { + strcpy(params->OutErrorMessage, "B must match A's type or be a scalar."); + return NOS_RESULT_FAILED; } - HandleEvent(CreateAppEvent( - fbb, - CreatePartialNodeUpdateDirect(fbb, &NodeId, nos::ClearFlags::NONE, nullptr, &pinsToAdd))); + // Resolve only the connected pin; the other input keeps its type. A connection on Output types A + // (Output has no input type to inherit). Output is then derived from the resulting A/B. + nos::Name aEff = (instigator == NSN_B) ? curA : nos::Name(params->IncomingTypeName); + nos::Name bEff = (instigator == NSN_B) ? nos::Name(params->IncomingTypeName) : curB; + for (size_t i = 0; i < params->PinCount; ++i) + { + auto& pin = params->Pins[i]; + nos::Name name(pin.Name); + if (name == NSN_B) + pin.OutResolvedTypeName = bEff; + else if (name == NSN_Output) + pin.OutResolvedTypeName = DeriveOutputType(aEff, bEff); + else + pin.OutResolvedTypeName = aEff; + } + return NOS_RESULT_SUCCESS; } - void SetOperator(reflect::BinaryOperator op, bool addTemplateParams) + void OnPinUpdated(const nosPinUpdate* update) override + { + if (update->UpdatedField != NOS_PIN_FIELD_TYPE_NAME) + return; + nos::Name pinName(update->PinName); + if (pinName == NSN_A) // A governs the node type + OnTypeUpdated(update->TypeName); + if (pinName == NSN_A || pinName == NSN_B) // an input changed: recompute Output + RefreshOutputType(); + } + + void OnTypeUpdated(nos::Name typeName) + { + Type = (typeName == NSN_TypeNameGeneric) ? std::nullopt : std::optional(nos::TypeInfo(typeName)); + } + + void RefreshOutputType() + { + auto* a = GetPin(NSN_A); + auto* b = GetPin(NSN_B); + auto* out = GetPin(NSN_Output); + if (!a || !b || !out) + return; + nos::Name derived = DeriveOutputType(a->TypeName, b->TypeName); + if (out->TypeName != derived) + SetPinType(NSN_Output, derived); + } + + void SetOperator(reflect::BinaryOperator op, bool persist) { Operator = op; + SetNodeDisplayName(BinaryOpToDisplayName(op)); + if (!persist) + return; + // Persist the chosen operator as a template parameter so it survives save/load. flatbuffers::FlatBufferBuilder fbb; - std::string displayNameStr = BinaryOpToDisplayName(op); - if constexpr (IsScalarArithmetic) - displayNameStr += " (Scalar)"; - auto displayName = fbb.CreateString(displayNameStr); - std::vector opData = PackObjectData(*Operator); + std::vector opData = PackObjectData(op); std::vector params = { fb::CreateTemplateParameterDirect(fbb, NSN_Operator.AsCStr(), "nos.reflect.BinaryOperator", &opData) }; auto templateParamsOffset = fbb.CreateVector(params); PartialNodeUpdateBuilder update(fbb); update.add_node_id(&NodeId); - if (addTemplateParams) - update.add_template_parameters(templateParamsOffset); - update.add_display_name(displayName); + update.add_template_parameters(templateParamsOffset); HandleEvent(CreateAppEvent(fbb, update.Finish())); } - bool IsTypeCompatible(TypeInfo& typeInfo) + // --- Type / operator menus --------------------------------------------------------------------- + + enum MenuCommand : uint32_t { - if (typeInfo->BaseType == NOS_BASE_TYPE_STRUCT && !typeInfo->ByteSize) - return false; - if (typeInfo->BaseType == NOS_BASE_TYPE_ARRAY || typeInfo->BaseType == NOS_BASE_TYPE_UNION) - return false; + RESET_PIN_TYPE = 1, + SET_PIN_TYPE_BASE = 0x10000, // + index into AvailableTypes: set the instigating pin's type + SET_NODE_TYPE_BASE = 0x20000, // + index into AvailableTypes: set every pin's type + SET_OPERATOR_BASE = 0x30000, // + BinaryOperator value: set the operator + }; - if (Operator != BinaryOperator::ADD && typeInfo->BaseType == NOS_BASE_TYPE_STRING) - return false; + std::vector AvailableTypes; - if (typeInfo->AttributeCount == 0) - return false; - for (int i = 0; i < typeInfo->AttributeCount; ++i) - if (typeInfo->Attributes[i].Name == NOS_NAME_STATIC("resource")) - return false; - return true; + std::vector const& GetAvailableTypes() + { + if (!AvailableTypes.empty()) + return AvailableTypes; + size_t count = 0; + if (nosEngine.GetPinDataTypeNames(nullptr, &count) == NOS_RESULT_FAILED) + return AvailableTypes; + std::vector names(count); + nosEngine.GetPinDataTypeNames(names.data(), &count); + for (auto n : names) + if (nos::TypeInfo info(n); info && IsArithmeticType(info)) + AvailableTypes.push_back(n); + std::sort(AvailableTypes.begin(), AvailableTypes.end(), + [](nos::Name a, nos::Name b) { return a.AsString() < b.AsString(); }); + return AvailableTypes; } - void SetType(nosName typeName) + // Build a type-picker menu, grouping namespaced types (e.g. nos.fb.*) under nested submenus. + void BuildTypeMenu(ContextMenuBuilder& menu, std::vector const& types, uint32_t commandBase) { - Type = nos::TypeInfo(typeName); - flatbuffers::FlatBufferBuilder fbb; - std::vector typeData = nos::Buffer(nos::Name(Type->TypeName).AsCStr(), 1 + nos::Name(Type->TypeName).AsString().size()); - std::vector params = {fb::CreateTemplateParameterDirect(fbb, NSN_Type.AsCStr(), "string", &typeData) - }; - auto templateParamsOffset = fbb.CreateVector(params); - PartialNodeUpdateBuilder update(fbb); - update.add_node_id(&NodeId); - update.add_template_parameters(templateParamsOffset); - HandleEvent(CreateAppEvent(fbb, update.Finish())); - if (!IsTypeCompatible(*Type)) + std::map> byNamespace; + std::vector topLevel; + for (size_t i = 0; i < types.size(); ++i) { - // Make pins orphan - SetPinOrphanState(NSN_A, nos::fb::PinOrphanStateType::ORPHAN, "Type not supported."); - if constexpr (!IsScalarArithmetic) - SetPinOrphanState(NSN_B, nos::fb::PinOrphanStateType::ORPHAN, "Type not supported."); - SetPinOrphanState(NSN_Output, nos::fb::PinOrphanStateType::ORPHAN, "Type not supported."); + auto name = types[i].AsString(); + auto pos = name.rfind('.'); + if (pos != std::string::npos) + byNamespace[name.substr(0, pos)].push_back(i); + else + topLevel.push_back(i); } + for (size_t i : topLevel) + menu.Item(types[i].AsString(), commandBase + uint32_t(i)); + for (auto const& [ns, indices] : byNamespace) + menu.Submenu(ns, [&](ContextMenuBuilder& sub) { + for (size_t i : indices) + sub.Item(types[i].AsString(), commandBase + uint32_t(i)); + }); } - void SetScalarType(nosName typeName) + void OnNodeMenuRequested(nosContextMenuRequestPtr request) override { - static_assert(IsScalarArithmetic); - ScalarType = nos::TypeInfo(typeName); + auto const& types = GetAvailableTypes(); + SendContextMenu(request, [&](ContextMenuBuilder& menu) { + menu.Submenu("Set Operator", [](ContextMenuBuilder& sub) { + for (auto op : reflect::EnumValuesBinaryOperator()) + sub.Item(BinaryOpToDisplayName(op), SET_OPERATOR_BASE + uint32_t(op)); + }); + menu.Submenu("Set Data Type For Node", [&](ContextMenuBuilder& sub) { + BuildTypeMenu(sub, types, SET_NODE_TYPE_BASE); + }); + }); } - void OnPinUpdated(const nosPinUpdate* update) override + void OnPinMenuRequested(nos::Name pinName, nosContextMenuRequestPtr request) override { - if (update->UpdatedField != NOS_PIN_FIELD_TYPE_NAME) - return; - bool isScalarPin = update->PinName == NSN_Scalar; - if constexpr (IsScalarArithmetic) - if (isScalarPin) - { - if (!ScalarType || ScalarType->TypeName != update->TypeName) - SetScalarType(update->TypeName); - return; - } - if (!Type || Type->TypeName != update->TypeName) - SetType(update->TypeName); + if (pinName == NSN_Output) + return; // Output type is derived from the inputs + auto const& types = GetAvailableTypes(); + SendContextMenu(request, [&](ContextMenuBuilder& menu) { + menu.Submenu("Set Data Type", [&](ContextMenuBuilder& sub) { + BuildTypeMenu(sub, types, SET_PIN_TYPE_BASE); + }); + menu.Item("Reset Type", RESET_PIN_TYPE); + }); } - nosResult OnResolvePinDataTypes(nosResolvePinDataTypesParams* params) override + void OnMenuCommand(uuid const& itemId, uint32_t cmd) override { - nos::TypeInfo incomingType(params->IncomingTypeName); - if constexpr (IsScalarArithmetic) + auto const& types = GetAvailableTypes(); + if (cmd >= SET_OPERATOR_BASE) + SetOperator(reflect::BinaryOperator(cmd - SET_OPERATOR_BASE), true); + else if (cmd >= SET_NODE_TYPE_BASE) { - bool isScalarPin = params->InstigatorPinName == NSN_Scalar; - for (size_t i = 0; i < params->PinCount; i++) - { - auto& pin = params->Pins[i]; - if ((pin.Name == NSN_Scalar) ^ isScalarPin) - pin.OutResolvedTypeName = NSN_TypeNameGeneric; - } - if (isScalarPin) - { - bool isTypeScalar = incomingType->BaseType == NOS_BASE_TYPE_INT || incomingType->BaseType == NOS_BASE_TYPE_UINT || incomingType->BaseType == NOS_BASE_TYPE_FLOAT; - if (!isTypeScalar) - { - strcpy(params->OutErrorMessage, "Scalar arithmetic requires a scalar type"); - return NOS_RESULT_FAILED; - } - return NOS_RESULT_SUCCESS; - } + if (size_t idx = cmd - SET_NODE_TYPE_BASE; idx < types.size()) + SetNodeType(types[idx]); } - /* - * This part is disabled since Interpolate node from nos.Animation uses this node in its path for non-suitable types, but it is not executed. Fix it gracefully later. - if (incomingType->BaseType == NOS_BASE_TYPE_STRUCT && !incomingType->ByteSize) - return NOS_RESULT_FAILED; - if (incomingType->BaseType == NOS_BASE_TYPE_ARRAY || incomingType->BaseType == NOS_BASE_TYPE_UNION) - return NOS_RESULT_FAILED; - - if (Operator != BinaryOperator::ADD && incomingType->BaseType == NOS_BASE_TYPE_STRING) - return NOS_RESULT_FAILED; - - if (incomingType->AttributeCount == 0) - return NOS_RESULT_SUCCESS; - for (int i = 0; i < incomingType->AttributeCount; ++i) + else if (cmd >= SET_PIN_TYPE_BASE) { - if (incomingType->Attributes[i].Name == NOS_NAME_STATIC("resource")) - { - strcpy(params->OutErrorMessage, "Resource types are not supported"); - return NOS_RESULT_FAILED; - } + if (size_t idx = cmd - SET_PIN_TYPE_BASE; idx < types.size() && itemId != NodeId) + SetPinType(itemId, types[idx]); } - */ - return NOS_RESULT_SUCCESS; + else if (cmd == RESET_PIN_TYPE && itemId != NodeId) + SetPinType(itemId, NSN_TypeNameGeneric); + } + + void SetNodeType(nos::Name typeName) + { + SetPinType(NSN_A, typeName); + SetPinType(NSN_B, typeName); + SetPinType(NSN_Output, typeName); } nosResult ExecuteNode(NodeExecuteParams const& params) override { - if (!Type || NSN_TypeNameGeneric == Type->TypeName || !Operator) + if (!Type || !Operator || NSN_TypeNameGeneric == (*Type)->TypeName) return NOS_RESULT_SUCCESS; - if constexpr (IsScalarArithmetic) - { - if (!ScalarType || NSN_TypeNameGeneric == ScalarType->TypeName) - return NOS_RESULT_SUCCESS; - } - flatbuffers::FlatBufferBuilder fbb; + auto* bPin = GetPin(NSN_B); + if (!bPin || bPin->TypeName == NSN_TypeNameGeneric) + return NOS_RESULT_SUCCESS; + nos::TypeInfo bType(bPin->TypeName); - if constexpr (!IsScalarArithmetic) + if ((*Type)->BaseType == NOS_BASE_TYPE_STRING) { - auto& B = params[NSN_B]; - // TODO: we can directly set execute node function ptr instead of switch case etc. - if ((*Type)->BaseType == NOS_BASE_TYPE_STRING) - { - std::stringstream ss; - ss << params.GetPinValue(NSN_A) << params.GetPinValue(NSN_B); - auto str = ss.str(); - SetPinValue(NSN_Output, str.c_str()); - } - else - { - nos::Buffer outputBuf = params.GetPinBuffer(NSN_Output); - DoOp(*Operator, - *Type, - params.GetPinValue(NSN_A), - params.GetPinValue(NSN_B), - outputBuf.As()); - SetPinValue(NSN_Output, outputBuf); - } + std::stringstream ss; + ss << params.GetPinValue(NSN_A) << params.GetPinValue(NSN_B); + auto str = ss.str(); + SetPinValue(NSN_Output, str.c_str()); + return NOS_RESULT_SUCCESS; } - else - { - nos::Buffer outputBuf = params.GetPinBuffer(NSN_Output); + + nos::Buffer outputBuf = params.GetPinBuffer(NSN_Output); + bool broadcast = nos::Name(bType->TypeName) != nos::Name((*Type)->TypeName) && IsScalarBaseType(*bType); + if (broadcast) DoScalarOp(*Operator, *Type, params.GetPinValue(NSN_A), - *ScalarType, - params.GetPinValue(NSN_Scalar), + *bType, + params.GetPinValue(NSN_B), outputBuf.As()); - SetPinValue(NSN_Output, outputBuf); - } - + else + DoOp(*Operator, + *Type, + params.GetPinValue(NSN_A), + params.GetPinValue(NSN_B), + outputBuf.As()); + SetPinValue(NSN_Output, outputBuf); return NOS_RESULT_SUCCESS; } - void OnMenuRequested(nosContextMenuRequestPtr request) override - { - if (Operator) - return; - flatbuffers::FlatBufferBuilder fbb; - std::vector> ops; - - for (auto op : reflect::EnumValuesBinaryOperator()) - ops.push_back(nos::CreateContextMenuItemDirect(fbb, BinaryOpToDisplayName(op), uint32_t(op))); - - HandleEvent(CreateAppEvent(fbb, CreateAppContextMenuUpdateDirect(fbb, &NodeId, request->pos(), request->instigator(), &ops))); - } - - void OnMenuCommand(uuid const& itemId, uint32_t cmd) override - { - if (Operator) - return; - SetOperator(reflect::BinaryOperator(cmd), true); - } - + // Fold older nodes into this one: the legacy nos.math._ nodes, the scalar-broadcast + // ScalarArithmetic, and the generic ArithmeticDynamic all migrate to Arithmetic. The engine routes them + // here via GetRenamedNodeClasses; this sees the original class name and rewrites the node buffer. static nosResult MigrateNode(nosFbNodePtr nodePtr, nosBuffer* outBuffer) { fb::TNode tNode; nodePtr->UnPackTo(&tNode); - std::optional op{}; - if (tNode.class_name.starts_with("nos.math.Add_")) - op = reflect::BinaryOperator::ADD; - else if (tNode.class_name.starts_with("nos.math.Sub_")) - op = reflect::BinaryOperator::SUB; - else if (tNode.class_name.starts_with("nos.math.Mul_")) - op = reflect::BinaryOperator::MUL; - else if (tNode.class_name.starts_with("nos.math.Div_")) - op = reflect::BinaryOperator::DIV; - // Dont migrate - if (!op) - return NOS_RESULT_SUCCESS; - auto& templateParam = tNode.template_parameters.emplace_back(std::make_unique()); - templateParam->name = NSN_Operator.AsString(); - templateParam->value = nos::Buffer::From(op); - - templateParam->type_name = "nos.reflect.BinaryOperator"; - tNode.class_name = "nos.reflect." + NSN_ArithmeticDynamic.AsString(); - std::string type = "float"; - for (auto& pin : tNode.pins) + const std::string cls = tNode.class_name; + bool fromScalar = cls == "nos.reflect.ScalarArithmetic"; + bool fromDynamic = cls == "nos.reflect.ArithmeticDynamic"; + + if (!fromScalar && !fromDynamic) { - type = pin->type_name; - if (pin->name == "X") - pin->name = NSN_A.AsString(); - else if (pin->name == "Y") - pin->name = NSN_B.AsString(); - else if (pin->name == "Z") - pin->name = NSN_Output.AsString(); + // Legacy nos.math._: derive the operator, capture the type, rename the X/Y/Z pins. + std::optional op{}; + if (cls.starts_with("nos.math.Add_")) + op = reflect::BinaryOperator::ADD; + else if (cls.starts_with("nos.math.Sub_")) + op = reflect::BinaryOperator::SUB; + else if (cls.starts_with("nos.math.Mul_")) + op = reflect::BinaryOperator::MUL; + else if (cls.starts_with("nos.math.Div_")) + op = reflect::BinaryOperator::DIV; + if (!op) + return NOS_RESULT_SUCCESS; // already an Arithmetic node (or nothing we migrate) + + auto& opParam = tNode.template_parameters.emplace_back(std::make_unique()); + opParam->name = NSN_Operator.AsString(); + opParam->value = nos::Buffer::From(op); + opParam->type_name = "nos.reflect.BinaryOperator"; + + std::string type = "float"; + for (auto& pin : tNode.pins) + { + type = pin->type_name; + if (pin->name == "X") + pin->name = NSN_A.AsString(); + else if (pin->name == "Y") + pin->name = NSN_B.AsString(); + else if (pin->name == "Z") + pin->name = NSN_Output.AsString(); + } + auto& typeParam = tNode.template_parameters.emplace_back(std::make_unique()); + typeParam->name = NSN_Type.AsString(); + typeParam->value = std::vector(PackObjectData(type.c_str())); + typeParam->type_name = "string"; } - auto& templateParam2 = tNode.template_parameters.emplace_back(std::make_unique()); - templateParam2->name = NSN_Type.AsString(); - templateParam2->value = std::vector(PackObjectData(type.c_str())); - templateParam2->type_name = "string"; + else if (fromScalar) + { + // The scalar operand pin becomes B; broadcast is inferred from B's scalar type at execute time. + for (auto& pin : tNode.pins) + if (pin->name == NSN_Scalar.AsString()) + pin->name = NSN_B.AsString(); + } + // fromDynamic: only the class name changes. + + tNode.class_name = "nos.reflect." + NSN_Arithmetic.AsString(); *outBuffer = EngineBuffer::CopyFrom(tNode).Release(); return NOS_RESULT_SUCCESS; } @@ -498,27 +524,9 @@ void RegisterArithmeticNodePresets() { for (auto& typeName : typeNames) { nos::TypeInfo typeInfo(typeName); - if (typeInfo->BaseType == NOS_BASE_TYPE_NONE) - continue; - // Don't show table types - if (typeInfo->BaseType == NOS_BASE_TYPE_STRUCT) { - if (!typeInfo->ByteSize) - continue; - // Don't show structs with no builtin attribute - bool skip = true; - for (int i = 0; i < typeInfo->AttributeCount; ++i) - { - if (typeInfo->Attributes[i].Name == NOS_NAME_STATIC("builtin")) - skip = false; - } - if (skip) - continue; - } - // Don't show enums and arrays - if (typeInfo->BaseType == NOS_BASE_TYPE_UNION || typeInfo->BaseType == NOS_BASE_TYPE_ARRAY || - IsEnumType(typeInfo)) + if (!ArithmeticNode::IsArithmeticType(typeInfo)) continue; - + // Non-ADD operators don't apply to strings (only concatenation is defined). if (binaryOp != BinaryOperator::ADD && typeInfo->BaseType == NOS_BASE_TYPE_STRING) continue; @@ -543,26 +551,14 @@ void RegisterArithmeticNodePresets() { std::vector fbNodePresets; for (auto& buf : nodePresets) fbNodePresets.push_back(flatbuffers::GetMutableRoot(buf.Data())); - nosEngine.RegisterNodePresets(nos::Name("nos.reflect." + NSN_ArithmeticDynamic.AsString()), fbNodePresets.size(), fbNodePresets.data()); + nosEngine.RegisterNodePresets(NSN_Arithmetic, fbNodePresets.size(), fbNodePresets.data()); } nosResult RegisterArithmetic(nosNodeFunctions* fn) { - NOS_BIND_NODE_CLASS(NSN_Arithmetic, ArithmeticNodeContext, fn); - return NOS_RESULT_SUCCESS; -} - -nosResult RegisterArithmeticDynamic(nosNodeFunctions* fn) -{ - NOS_BIND_NODE_CLASS(NSN_ArithmeticDynamic, ArithmeticNodeContext, fn); + NOS_BIND_NODE_CLASS(NSN_Arithmetic, ArithmeticNode, fn); RegisterArithmeticNodePresets(); return NOS_RESULT_SUCCESS; } -nosResult RegisterScalarArithmetic(nosNodeFunctions* fn) -{ - NOS_BIND_NODE_CLASS(NSN_ScalarArithmetic, ArithmeticNodeContext, fn); - return NOS_RESULT_SUCCESS; -} - -} // namespace nos \ No newline at end of file +} // namespace nos::reflect diff --git a/Plugins/nosReflect/Source/PluginMain.cpp b/Plugins/nosReflect/Source/PluginMain.cpp index e1514427..fbeaa199 100644 --- a/Plugins/nosReflect/Source/PluginMain.cpp +++ b/Plugins/nosReflect/Source/PluginMain.cpp @@ -24,8 +24,6 @@ enum Nodes : size_t Array, Delay, Arithmetic, - ArithmeticDynamic, - ScalarArithmetic, IndexOf, IsEqual, GreaterThan, @@ -49,8 +47,6 @@ nosResult RegisterIndexer(nosNodeFunctions* node); nosResult RegisterArray(nosNodeFunctions* node); nosResult RegisterDelay(nosNodeFunctions* node); nosResult RegisterArithmetic(nosNodeFunctions* node); -nosResult RegisterArithmeticDynamic(nosNodeFunctions* node); -nosResult RegisterScalarArithmetic(nosNodeFunctions* node); nosResult RegisterIndexOf(nosNodeFunctions* node); nosResult RegisterIsEqual(nosNodeFunctions* node); nosResult RegisterGreaterThan(nosNodeFunctions* node); @@ -91,14 +87,12 @@ nosResult NOSAPI_CALL ExportNodeFunctions(size_t* outCount, nosNodeFunctions** o GEN_CASE_NODE(Array) GEN_CASE_NODE(Delay) GEN_CASE_NODE(Arithmetic) - GEN_CASE_NODE(ScalarArithmetic) GEN_CASE_NODE(IndexOf) GEN_CASE_NODE(IsEqual) GEN_CASE_NODE(GreaterThan) GEN_CASE_NODE(LessThan) GEN_CASE_NODE(SetVariable) GEN_CASE_NODE(GetVariable) - GEN_CASE_NODE(ArithmeticDynamic) GEN_CASE_NODE(EnumToUnderlyingValue) GEN_CASE_NODE(EnumFromUnderlyingValue) GEN_CASE_NODE(CopyingRingBuffer) @@ -131,7 +125,9 @@ NOSAPI_ATTR nosResult NOSAPI_CALL nosExportPlugin(nosPluginFunctions* outPluginF constexpr auto types = std::array{"f32", "f64", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "vec2", "vec2i", "vec2u", "vec2d", "vec3", "vec3i", "vec3u", "vec3d", "vec4", "vec4i", "vec4u", "vec4d"}; - *outCount = ops.size() * types.size(); + // The scalar-broadcast and generic arithmetic nodes are folded into nos.reflect.Arithmetic; route + // their saved instances there too. Arithmetic::MigrateNode fixes up pins/params afterwards. + *outCount = ops.size() * types.size() + 2; if (!outRenamedFrom) return; auto idx = 0; @@ -142,6 +138,12 @@ NOSAPI_ATTR nosResult NOSAPI_CALL nosExportPlugin(nosPluginFunctions* outPluginF outRenamedTo[idx] = NOS_NAME("nos.reflect.Arithmetic"); idx++; } + outRenamedFrom[idx] = NOS_NAME("nos.reflect.ScalarArithmetic"); + outRenamedTo[idx] = NOS_NAME("nos.reflect.Arithmetic"); + idx++; + outRenamedFrom[idx] = NOS_NAME("nos.reflect.ArithmeticDynamic"); + outRenamedTo[idx] = NOS_NAME("nos.reflect.Arithmetic"); + idx++; }; return NOS_RESULT_SUCCESS; }