End of my rope here

Ive been debugging about a day now and Im sure I'm missing something obvious:
Im using a function within a class to register another node:

 node.on("input", function(msg, send, done) {
        try {

          switch(msg.topic) {
            case 'registerChild':
              m.childRegistrationUtils.registerChild(msg.payload ,msg.positionVsParent);

until recently this went smoothly however after I started using a common registerChild function which looks like this:

// ChildRegistrationUtils.js
class ChildRegistrationUtils {
  constructor(mainClass) {
      this.mainClass = mainClass; // Reference to the main class
      this.logger = mainClass.logger;
  }

  registerChild(child,positionVsParent) {
      const { softwareType } = child.config.functionality;
      const { name, id, unit } = child.config.general;
      const { type, subType } = child.config.asset;
      const emitter = child.emitter;

      if (!this.mainClass.child) this.mainClass.child = {};
      if (!this.mainClass.child[softwareType]) this.mainClass.child[softwareType] = {};
      if (!this.mainClass.child[softwareType][type]) this.mainClass.child[softwareType][type] = {};
      if (!this.mainClass.child[softwareType][type][subType]) this.mainClass.child[softwareType][type][subType] = {};

      // Use an array to handle multiple subtypes
      if (!Array.isArray(this.mainClass.child[softwareType][type][subType])) {
          this.mainClass.child[softwareType][type][subType] = [];
      }

Etc... registering the same measurement node on 2 different machine nodes:

  connectMeasurement(emitter, subType, position) {

    this.logger.debug(`Connecting measurement child: ${subType} with position=${position}`);
try{
      emitter.on("mAbs", (value) => {

          // Hard-coded debug to prove it's the second machine
        if (this.mainClass.config.general.name === "m1") {
            console.log("\n[M1 CALLBACK FIRED]", "value=", value);
        }

                  // Hard-coded debug to prove it's the second machine
                  if (this.mainClass.config.general.name === "m2") {
                    console.log("\n[M2 CALLBACK FIRED]", "value=", value);
                }

never fires the callback of the second machine.
I see 2 listners, ... i have tried everything but can find no errors.
The only thing that I can think of is that the id gets overwritten somewhere of my measurement node or that it has to do with the way I'm passing the reference of the mainclass to my function as this ?

what do you guys think?

unfortunately, not nearly enough information to give you a definitive answer.

However, if you suspect "by reference" issues, then use the provided utility function RED.util.cloneMessage in your on('input',... handler to rule it out.

Hey STeve,

Yeah understood what you are trying to do however this destroys my reference to the emitter of that node. So I didnt want to make a lengthy post so Ill try to give as much as possible without overkilling. (its good for my thought process as well so I dont mind sharing).

This is my flow:

Here is my measurement.js (node red interface) that sends the msg on deploy to all the other nodes. This has always worked fine when directly handled by the receiving class.

// register child on first output this timeout is needed because of node - red stuff
      setTimeout(
        () => {
          
          /*---execute code on first start----*/
          let msgs = [];

          msgs[2] = { topic : "registerChild" , payload: source, positionVsParent: "upStream" };
          msgs[3] = { topic : "registerChild" , payload: source, positionVsParent: "downStream" };
          
          //send msg
          this.send(msgs);
        }, 
        100
      );

My receiving class does:

      node.on("input", function(msg, send, done) {
        try {

          switch(msg.topic) {
            case 'registerChild':
              m.childRegistrationUtils.registerChild(msg.payload ,msg.positionVsParent);
              break;

Etc.. but that code is all between try's and catches so nothing goes wrong there and I also see stuf happening in my debug statements so far so good.

This m stands for my machine node, calls a shared childRegistrationUtils script which calls the function registerChild and passes the child and the position of the child vs the parent. I have upStream and downStream variants.

My complete class:

// ChildRegistrationUtils.js
class ChildRegistrationUtils {
  constructor(mainClass) {
    this.mainClass = mainClass; // Reference to the main class
    this.logger = mainClass.logger; // Access logger directly from main class
  }

  registerChild(child, positionVsParent) {
    const { softwareType } = child.config.functionality;
    const { name, id, unit } = child.config.general;
    const { type, subType } = child.config.asset;
    const emitter = child.emitter;

    // Ensure nested structures
    if (!this.mainClass.child) this.mainClass.child = {};
    if (!this.mainClass.child[softwareType]) {
      this.mainClass.child[softwareType] = {};
    }
    if (!this.mainClass.child[softwareType][type]) {
      this.mainClass.child[softwareType][type] = {};
    }
    if (!this.mainClass.child[softwareType][type][subType]) {
      this.mainClass.child[softwareType][type][subType] = [];
    }

    // Check if we already have an entry with the same 'id'
    const existing = this.mainClass.child[softwareType][type][subType].filter(
      (c) => c.id === id
    );
    if (existing.length > 0) {
      this.logger.warn(
        `Child with ID '${id}' already exists. Registering an additional instance anyway.`
      );
    }

    // Push (add) the new child object to the array no matter what
    this.mainClass.child[softwareType][type][subType].push({
      name,
      id,
      unit,
      emitter
    });

    // Then connect the child depending on its 'desc'
    // We keep calling connectChild so each instance gets its own listener
    this.connectChild(id, softwareType, emitter, type, child, subType, positionVsParent);
  }

  connectChild(id, desc, emitter, type, child, subType, positionVsParent) {
    this.logger.debug(
      `Connecting child id=${id}: desc=${desc}, type=${type}, subType=${subType}, position=${positionVsParent}`
    );

    switch (desc) {
      case "measurement":
        this.logger.debug(`Registering measurement child: ${id} with type=${type}`);
        this.connectMeasurement(emitter, subType, positionVsParent);
        break;

      case "machine":
        this.logger.debug(`Registering complete machine child: ${id}`);
        this.connectMachine(child);
        break;

      default:
        this.logger.error(`Unrecognized desc: ${desc}`);
    }
  }

  connectMeasurement(emitter, subType, position) {
    this.logger.debug(
      `Connecting measurement child: ${subType} with position=${position}`
    );

    // Attach a new listener for every registration
    emitter.on("mAbs", (value) => {
      // Hard-coded debug to prove it's the second machine
      if (this.mainClass.config.general.name === "m1") {
        console.log("\n[M1 CALLBACK FIRED]", "value=", value);
      }
      if (this.mainClass.config.general.name === "m2") {
        console.log("\n[M2 CALLBACK FIRED]", "value=", value);
      }

      this.logger.debug(`Checking position ${position} of measurement`);
      if (position === "upStream") {
        this.mainClass.measurements
          .type(subType)
          .variant("measured")
          .setUpstream(value);

        this.logger.debug(
          ` subType : ${subType} updated to ` +
            this.mainClass.measurements.type(subType).variant("measured").getUpstream()
        );
      } else if (position === "downStream") {
        this.mainClass.measurements
          .type(subType)
          .variant("measured")
          .setDownstream(value);

        this.logger.debug(
          ` subType : ${subType} updated to ` +
            this.mainClass.measurements.type(subType).variant("measured").getDownstream()
        );
      } else {
        this.logger.error(`Position ${position} not recognized!`);
      }

      this.mainClass.updateMeasurement(subType);
      this.logger.debug("---- End of the emit function ----- ");
    });
  }

  connectMachine(machine) {
    if (!machine) {
      this.logger.warn("Invalid machine provided.");
      return;
    }

    // Example: you create a new machineId for each registration
    const machineId = Object.keys(this.mainClass.machines).length + 1;
    this.mainClass.machines[machineId] = machine;
    this.mainClass.outputs.machineOutputs[machineId] = { flow: 0, power: 0 };

    this.logger.info(`Initializing combination curves for machine ${machineId}`);

    this.mainClass.updateDynamicTotals();

    this.logger.info(`Setting up pressureChange listener for machine ${machineId}`);
    machine.emitter.on("pressureChange", () => {
      this.mainClass.handlePressureChange(machine);
    });
    this.logger.info(`Machine ${machineId} registered successfully.`);
  }
}

module.exports = ChildRegistrationUtils;

I hardcoded the m1 and m2 name to match the node names just to see if it fires because in my own debug I can see the following:

[DEBUG] -> m2: Connecting child id=6aa12d48388bad0c: desc=measurement, type=sensor, subType=pressure, position=downStream
[DEBUG] -> m2: Registering measurement child: 6aa12d48388bad0c with type=sensor
[DEBUG] -> m2: Connecting measurement child: pressure with position=downStream
[DEBUG] -> m1: Connecting child id=6aa12d48388bad0c: desc=measurement, type=sensor, subType=pressure, position=downStream
[DEBUG] -> m1: Registering measurement child: 6aa12d48388bad0c with type=sensor
[DEBUG] -> m1: Connecting measurement child: pressure with position=downStream
[WARN] -> PT1: Simulated value 0 is outside of abs range constraining between min=800 and max=1500
[WARN] -> PT1: New value=0 is constrained to fit between min=800 and max=1500
[DEBUG] -> PT1: Applying smoothing method "none"
[WARN] -> PT1: Output value=788.889471739861 is outside of ABS range. Constraining.
[WARN] -> PT1: New value=788.889471739861 is constrained to fit between min=800 and max=1500
[DEBUG] -> PT1: Emitting mAbs=800
[M2 CALLBACK FIRED] value= 800
[DEBUG] -> m2: Checking position downStream of measurement
[DEBUG] -> m2: subType : pressure updated to 800
[DEBUG] -> m2: Pressure measurements - Upstream: null, Downstream: 800
[DEBUG] -> m2: Using downstream pressure: 800
[DEBUG] -> m2: ---- End of the emit function -----
[WARN] -> PT1: Simulated value 788.889471739861 is outside of abs range constraining between min=800 and max=1500
[WARN] -> PT1: New value=788.889471739861 is constrained to fit between min=800 and max=1500
[DEBUG] -> PT1: Applying smoothing method "none"
[DEBUG] -> PT1: Emitting mAbs=816

Here we see no M1 callback fired. The number of the node is not of importance I suspect its always only showing the first node that registered this child.

Perhaps overkill but if you want I can also share the class that holds the measurements:


class MeasurementContainer {
    constructor() {
      this.measurements = {};
    }
  
    type(mainKey) {
      if (!this.measurements[mainKey]) {
        this.measurements[mainKey] = {};
      }
      return new SubKeyHandler(this, mainKey);
    }
  
    _getOrCreateMeasurement(mainKey, subKey) {
      if (!this.measurements[mainKey][subKey]) {
        this.measurements[mainKey][subKey] = new MeasurementBuilder()
          .setType(`${mainKey}-${subKey}`)
          .build();
      }
      return this.measurements[mainKey][subKey];
    }
  
    getMeasurement(mainKey, subKey) {
      return this.measurements[mainKey]?.[subKey];
    }
  
    removeMeasurement(mainKey, subKey) {
      if (!this.measurements[mainKey]) return this;
  
      if (subKey) {
        delete this.measurements[mainKey][subKey];
        if (Object.keys(this.measurements[mainKey]).length === 0) {
          delete this.measurements[mainKey];
        }
      } else {
        delete this.measurements[mainKey];
      }
      return this;
    }

    //returns difference between actual and predicted values
    getDifferenceActualPredicted(mainKey) {
        if (this.measurements[mainKey]) {
            let subKeys = Object.keys(this.measurements[mainKey]);
            if (subKeys.length == 2) {
                let actual = this.measurements[mainKey][subKeys[0]].downstream;
                let predicted = this.measurements[mainKey][subKeys[1]].downstream;
                return actual - predicted;
            }
            else {
                return null;
            }
        }
    }


    addMeasurement(mainKey, subKey, measurement) {
      if (!this.measurements[mainKey]) {
        this.measurements[mainKey] = {};
      }
      if (!(measurement instanceof Measurement)) {
        measurement = new MeasurementBuilder()
          .setType(`${mainKey}-${subKey}`)
          .build();
      }
      this.measurements[mainKey][subKey] = measurement;
      return this;
    }

    getSubKeys(mainKey) {
      return this.measurements[mainKey] ? new Set(Object.keys(this.measurements[mainKey])) : new Set();
    }
}
  
class SubKeyHandler {
    constructor(container, mainKey) {
      this.container = container;
      this.mainKey = mainKey;
    }
  
    variant(subKey) {
      return this.container._getOrCreateMeasurement(this.mainKey, subKey);
    }
}
  
class Measurement {
    constructor(type) {
      this.type = type;               // e.g. 'pressure', 'flow', etc.
      this.upstream = null;       // upstream measurement value
      this.downstream = null;   // downstream measurement value
    }
  
    // -- Updater methods --
    setType(type) {
      this.type = type;
      return this;
    }
  
    setUpstream(upstream) {
      this.upstream = upstream;
      return this;
    }
  
    setDownstream(downstream) {
      this.downstream = downstream;
      return this;
    }

    hasDownUp() {
        return this.upstream != null && this.downstream != null? true : false;
    }
  
    // -- Calculators --
    getDifferential() {
      if (this.valideDiff()) {
        return this.downstream - this.upstream;
      }
      return null;
    }

    getDownstream() {
        return this.downstream;
    }

    getUpstream() {
      return this.upstream;
    }

    // helper functions
    valideDiff() {
        return this.upstream != null && this.downstream != null? true : false;
    }
}
  
class MeasurementBuilder {
    constructor() {
      this.type = null;
    }
  
    setType(type) {
      this.type = type;
      return this;
    }
  
    build() {
      return new Measurement(
        this.type,
      );
    }
}

module.exports = MeasurementContainer;

/*
// -- Example usage --
// 1) Create a new container
const container = new MeasurementContainer();

// 2) Create or get a measurement with:
//    - mainKey = "pressure"
//    - subKey  = "predicted"
//    Then set upstream/downstream on that measurement.
container
    .type("pressure")             // Stage 1: mainKey
    .variant("predicted")         // Stage 2: subKey
    .setUpstream(42)

    console.log(
container
.type("pressure")             // Stage 1: mainKey
.variant("predicted")         // Stage 2: subKey
.getUpstream()
    );


// Retrieve and log the differential
console.log("Predicted Pressure Differential:", container.getMeasurement("pressure","predicted").getDifferential()); 
console.log("Get difference between actual and predicted " , container.getDifferenceActualPredicted("pressure"));
// => 50 - 42 = 8

// 3) Do the same for "pressure" -> "actual"
container
    .type("pressure")
    .variant("predicted")
    .setUpstream(45)
    .setDownstream(49);

    console.log("Predicted Pressure Differential:", container.getMeasurement("pressure","predicted").getDifferential()); 

// 3) Do the same for "pressure" -> "actual"
container
    .type("pressure")
    .variant("actual")
    .setUpstream(45)
    .setDownstream(49);

console.log("Predicted Pressure Differential:", container.getMeasurement("pressure","actual").getDifferential()); 
// => 49 - 45 = 4

// 4) Add a "temperature" measurement:
container
    .type("temperature")
    .variant("current")
    .setUpstream(20)
    .setDownstream(25);

const temperatureCurrent = container.getMeasurement("temperature", "current");
console.log("Current Temperature Differential:", temperatureCurrent.getDifferential()); 
// => 25 - 20 = 5

// 5) Check what sub-keys exist under "pressure"
const pressureVariants = container.getSubKeys("pressure");
console.log('Sub-keys for "pressure":', pressureVariants);
// => e.g. ["predicted", "actual"]

// 6) Remove one sub-key, e.g. "predicted"
container.removeMeasurement("pressure", "predicted");
console.log('After removal, sub-keys for "pressure":', container.getSubKeys("pressure"));
// => e.g. ["actual"]

// 7) (Optional) Remove entire "pressure" by omitting subKey
container.removeMeasurement("pressure");
console.log('After removing "pressure":', container.getSubKeys("pressure")); 
// => []

// 8) We still have "temperature" -> "current" in the container.
console.log('Sub-keys for "temperature":', container.getSubKeys("temperature")); 
// => ["current"]
//*/

And if needed I can share the rest of my functions of the machine node I made but really suspect Im not seeing something that's really clear for someone with a lot of years of javascript / node red coding.

Ok so coming closer to the root cause.. its somewhere here:

Node id: m2
EventEmitter {
  _events: [Object: null prototype] { mAbs: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(shapeMode)]: false,
  [Symbol(kCapture)]: false
}
Node id: m1
EventEmitter {
  _events: { mAbs: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(shapeMode)]: false,
  [Symbol(kCapture)]: false
}

Notice on how my debug is spitting out 2 different evenEmitters while they both come from 1 message? why is this happening when Im sending it like this:

// register child on first output this timeout is needed because of node - red stuff
      setTimeout(
        () => {
          
          /*---execute code on first start----*/
          let msgs = [];

          msgs[2] = { topic : "registerChild" , payload: source.emitter, positionVsParent: "upStream" };
          msgs[3] = { topic : "registerChild" , payload: source.emitter, positionVsParent: "downStream" };
          
          //send msg
          this.send(msgs);
        }, 
        100
      );

As above they are both connected to downstream.

node.on("input", function(msg, send, done) {
        try {

          switch(msg.topic) {
            case 'registerChild':
              console.log(`Node id: ${node.name}`);
              console.log(msg.payload);
              m.registerChild(msg.payload);
              //m.childRegistrationUtils.registerChild(msg.payload ,msg.positionVsParent);
              break;

I simplified everything with the class but now also here it happens:

registerChild(emitter){
    emitter.on("mAbs", (value) => {
        
      this.logger.debug(`hello there`);
    });

  }

For the love of ... why is this happening? I never seemed to have this issue :grimacing:

Again here some more information:

Node id: m2
Payload reference: EventEmitter {
  _events: [Object: null prototype] { mAbs: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(shapeMode)]: false,
  [Symbol(kCapture)]: false
}
Payload type: object
Payload is event emitter: true
Payload events: [Object: null prototype] { mAbs: [Function (anonymous)] }
Payload constructor: EventEmitter
EventEmitter {
  _events: [Object: null prototype] { mAbs: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(shapeMode)]: false,
  [Symbol(kCapture)]: false
}
Node id: m1
Payload reference: EventEmitter {
  _events: { mAbs: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(shapeMode)]: false,
  [Symbol(kCapture)]: false
}
Payload type: object
Payload is event emitter: true
Payload events: { mAbs: [Function (anonymous)] }
Payload constructor: EventEmitter
EventEmitter {
  _events: { mAbs: [Function (anonymous)] },
  _eventsCount: 1,
  _maxListeners: undefined,
  [Symbol(shapeMode)]: false,
  [Symbol(kCapture)]: false
}

seems that the wires are responsible for transforming my object. Doesnt matter if I send it through an object or directly over to the other node. Something is messing with my reference?

Any insights on what could be happening here? Starting to suspect node red is buggy?

It maybe that I'm just to dumb to work it out but for the life of me I cannot work out what you are trying to achieve with those event emitters that couldn't maybe be done more simply.

Maybe if you described what you are trying to achieve, we could help with some ideas on how to do it in another way.

Haha. The idea is simply that on deploy my custom node sends a msg with the emitter to all other connected nodes. This tells the other nodes which child nodes are connected to it and if its the right kind trigger some kind of action. Like the on event emitter. I changed my last example i posted here.

There are other ways like using 1 global emitter function or using the context to store the emitter but this should also be working. And it does as long as i only have 1 node connected it works. But as soon as there are 2 nodes connected node red does something buggy with the same msg. So i really want to know why.

If you want to reproduce the bug it shouldn't be to hard if you follow my simple example.

It's difficult to understand why you want to do this?

Nodes do (should) communicate via wires not invisible event emitters/receivers.

At a guess, you could simply communicate status etc across your nodes using the config pattern (each node uses a common config node)

I have a bit more complexity then just status. How would you make a group controller for multiple child's without the controller having access to the child's classes and all the methods? In my case the group controller control the pumps and so it needs to know / use all the nodes capabilities. So without having back and forward constant q and answers or some kind of perpetual tick in the child or parent node i wanted it simpler.
And to be fair it works quite well outside of node red... I just don't get why the second node doesn't register exactly the same information.
Anyhow if you guys don't see my issue either the I guess I need to find a work around the bug or you would have a brilliant idea on how to tackle the thing that I want in a better way?

Last note in this: perhaps I'm thinking too much old school oop and not enough node red input output. The only thing that I wanted to avoid is write another big list of all the things I want to get done on each input from another node but perhaps that's also the strength of the wiring system.
Anyhow let's just close this I think I have my answer.

Not sure you've entirely grasped how Node-RED is doing things. You could, for example, simply grab references to all of your nodes since they all exist and can be found. Then you can access them directly.

As a simple example, have a look at the uibuilder nodes that link to a master uibuilder node. For example, the sender node. These let you select a master uibuilder node and will use the master directly without the need for a wired flow. Though as Steve intimates, you really should use this kind of thing sparingly - generally best to stick with wires where possible.

Anyway, I don't use emitters for these links but instead directly access the required node.

In fact, if you check out the latest node uib-sidebar in the v7.2.0 branch of uibuilder, you will see an even more extreme example because all uib-sidebar nodes communicate with a single sidebar and therefore they all need to access the same master HTML template and when you update the template in one node, all of the others need to be able to access the shared template.

1 Like

Hey @TotallyInformation, => Cleaned up my usual scribles a bit so others might benefit from it.

Your insights led me to a clean and effective solution for dynamically linking parent and child nodes in Node-RED without cluttering the UI with excess wires.

The Problem (bug) : Node-RED Clones Message Payloads

I discovered that passing objects through msg.payload can introduce inconsistencies due to Node-RED’s internal cloning behavior. My EventEmitter was being altered unexpectedly when sent through a message.

Instead of relying on msg.payload for passing object references, I realized I could leverage Node-RED's built-in node registry (RED.nodes.getNode(id)) to retrieve the correct parent instance dynamically.


The Solution: Hybrid Parent-Child Registration

To avoid excessive wires while keeping my architecture modular, I implemented a hybrid approach where:

  1. Each child node sends a registration request to the parent using node.id at startup using 1 wire.
  2. The parent node dynamically retrieves and registers child nodes using RED.nodes.getNode(childId).
  3. The child-parent relationship is automatically managed—users don't need to worry about connecting every child node manually.

Step 1: Child Nodes Register Themselves at Startup

Each child node sends its ID to its parent node via a message at startup.

setTimeout(() => {
    let msgs = [];

    // Notify parent nodes of this child node's existence
    msgs[2] = { topic: "registerChild", payload: node.id, positionVsParent: "upStream" };
    msgs[3] = { topic: "registerChild", payload: node.id, positionVsParent: "downStream" };

    // Send registration messages
    this.send(msgs);
}, 100);
  • The child only sends an ID, preventing object cloning issues.
  • No need to manually wire every child to its parent—Node-RED will handle the routing.
  • This ensures a clean UI with minimal wiring.

Step 2: Parent Nodes Dynamically Retrieve and Register Children

The parent node listens for registerChild messages and registers the child using its ID.

node.on("input", function (msg, send, done) {
    switch (msg.topic) {
        case 'registerChild':
            const childId = msg.payload;
            const childObj = RED.nodes.getNode(childId); // Retrieve child node instance

            // Register the child in the correct position
            m.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
            break;
    }
});
  • The parent dynamically retrieves the correct child instance from Node-RED’s registry.
  • No need to pass complex object references through messages.
  • The architecture remains modular and easy to maintain.

Why This Approach Is Ideal for me

Avoids Cloning Issues

  • Using RED.nodes.getNode(id) ensures we always reference the actual node, not a modified clone.

Removes UI Clutter

  • No unnecessary wires—users only connect essential process flows.

Ensures Automatic Parent-Child Registration

  • Children automatically find and register with their parents at startup.

Maintains Standard Node-RED Behavior

  • The architecture still supports standard wired outputs for debugging or additional logic.

Gives me a process architecture

  • I wanted a process like way to visualize the nodes and leverage my OOP style library.

Thanks again for the inspiration, @TotallyInformation! Hope this helps others looking for a different way to structure their Node-RED flows.

Here is an example using downstream assets:

Maybe I'm missing something, but rushing through the whole topic, I had the feeling:

  • Your main problem is to avoid "too much wires".

If there is a (small?) part true of this, I have to ask:

  • Did you know about "Link-In" and "Link-Out" nodes?

Using these you can choose to send messages to multiple! nodes using simple list of checkboxes.

If you want to send the end result "back to origin" (for example to handle button states) you can add an extra value to the message, so the origin knows when to exit the (otherwise endless) loop.

msg.alreadyProcessedMsg = true;

@PizzaProgram
Yeah it started out mainly with having alot of back and forth wires. But then it became something of a nuisance with a lot of my code needing functions of the other node.

Good point about the linked in functionality but yes that's something of a basic part see example I use:

Here is my own take on why I did what I did (I needed to explain this to others anyway so no harm in sharing this with the community and see if you feel the same on these topics):

Criteria Sending Objects Back & Forth Registering Object Reference
Performance :red_circle: High CPU overhead (serialization, copying) :green_circle: Low CPU overhead (direct access)
Memory Usage :red_circle: Can cause memory bloat :green_circle: Lower memory footprint
Maintainability :green_circle: Safer (no unintended mutations) :red_circle: Requires careful mutation control
Synchronization :red_circle: Risk of inconsistencies if updates are delayed :green_circle: Always up-to-date but requires mutation safeguards
Best Use Case For stateless, independent nodes For real-time updates and large objects

Seeing as I work with nodes that need to control realtime processes this approach was chosen. This doesn't mean that wires also couldn't work. Theres nothing wrong with sending the object over the wires and doing so for every event. However I have a lot of event updates for example when a machine travels from current value to setpoint.