DA.SetDataList: How to replace list?

Every time, the following command gets called, it appends to the list:

DA.SetDataList(8, new[] { 0 });

Result:

How do I make DA.SetDataList(8, new[] { 0 }) replace the list so that the result is a list with a single 0 in it?

Have you tried:

    var data = new int[1];
    // ...    
    DA.SetDataList(8, data);

// Rolf

Same result, as (I) expected. The list still is increasing in size.

Background: The code is run every time there is new data from an external server.

Looking into ExpireSolution() and ClearData() now…

Calling Clear() or ExpireSolution(false) prior to assigning to the list seems to do the trick.

I’ve never seen anything like that, unless multiple indata items is triggering the same solution several times, or when using a class level variable which is persistent between runs.

If you have multiple indata items triggering the component (see “A_item” pictured below) and you kill all results but the last one then… well, then your output doesn’t reflect the input in any meaningful way.

If the input is instead a list (when the indata is in fact a list), the component run only once for the entire list (see “B_list” input)

// Rolf

As I wrote above: The code is run every time there is new data from an external server.

See the C# code below, stripped down. The relevant part is the receive() function:

  1. Asynchronously wait for data from the server.

  2. Once data is available:

    • Process that data, and

    • call receive() (recursion step).

The next step is to automatically update all downstream components. I tried calling ExpireDownStreamObjects() after processing the data. This did not work reliably. Most of the time, the output parameters are reported to be empty (“no data collected” message). Apparently calling ExpireDownStreamObjects() is better avoided in that context. I don’t really understand the Grasshopper fundamentals, and I’m new to C#.

Code:

// ...
public class SanWebSocket : GH_Component
{
    private ClientWebSocket webSocket;
    // ...

    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
        // ...
        pManager.AddTextParameter("NodeIds", "nodeIds", "Node IDs", GH_ParamAccess.list);
        // ...
    }

    // ...

    private void parseGraphMessage(string message, IGH_DataAccess DA)
    {
        // ...
        ClearData(); // <- WHAT WE'RE TALKING ABOUT
        // ...
        DA.SetDataList(8, graph.nodeIds);
        // ...
    }

    private void parseMessage(string message, IGH_DataAccess DA)
    {
        // ...
        parseGraphMessage(message, DA);
        // ...
    }

    private void parseWebSocketBuffer(WebSocketReceiveResult receiveResult, ArraySegment<byte> buffer, IGH_DataAccess DA)
    {
        // ...
        parseMessage(message, DA);
    }

    private void receive(IGH_DataAccess DA)
    {
        // ...
        webSocket.ReceiveAsync(buffer, CancellationToken.None).ContinueWith(res =>
        {
            parseWebSocketBuffer(res.Result, buffer, DA);
            receive(DA); // <- INFINITE LOOP
        });
    }

    protected override async void SolveInstance(IGH_DataAccess DA)
    {
        // ... <- CREATE WEB SOCKET IF NEEDED
        receive(DA);
    }

    // ...
}
// ...

It’s curious, if you call DA.SetData(), the data is overwritten with each call (even within SolveInstance().

DA.SetDataList() is accumulating the data when called multiple times. I wonder, if this is intentional. Same for SetDataTree actually, the trees get merged…

        DA.SetDataList(0, new int[] { 1 });
        DA.SetDataList(0, new int[] { 2 });
        DA.SetDataList(0, new int[] { 3 });
        DA.SetDataList(0, new int[] { 4 });

produces:

image

What I would do is change the design pattern, restrict it to the flow of GH, so that:

  • when a new message is received, the component will rerun (calling ExpireSolution()) and nothing else.
  • All message processing/logic within SolveInstance, to be counted when it’s his turn.

Adapting it to the normal behavior of the components (with the difference of resetting it when a new message arrives) you should not have strange behaviors.

With list or tree access, the parameter expects a collection, and it doesn’t make sense to replace it with another collection because no subsequent component will be aware of this change (since the outputs are only updated when SolveInstance is finished), so adding a new collection to the existing collection is the logical behavior. Not sure because you get two branches instead of one, though. :thinking:

I think generally it is an unexpected behaviour to call DA.Set…() multiple times within SolveInstance(), I would have expected that the data only gets handled afterwards anyways? Like at some point the component and everything downstream expires, then the components get solved one at a time and the data gets passed on downstream. And at least for the SetDataTree() i find it a rather unexpected behaviour to accumulate the trees…

I now changed the algorithm to something that I think is cleaner:

  1. Start a task that asynchronously waits for a message from the external server.

  2. Once a complete message has been received, expire the solution, forcing a recompute. This clears all output parameters and calls SolveInstance().

  3. In SolveInstance():

    • Parse the message and populate the output parameters.

    • Go back to step 1, i.e. wait for the next message.