Skip to content

Data Exchange Examples v7

shakty edited this page Jul 4, 2023 · 7 revisions

Overview

There are 4 main functions to send data:

Command Description Recipient code required
node.say sends data to other clients or server yes, must implement node.on.data event listener
node.set sends data to server no, automatically inserted in node.game.memory database
node.done terminates current step, and calls node.set see node.set
node.get invokes a remote function, and waits for return value yes, must implement node.on event listener

and two functions to handle receiving data:

Command Description
node.on catches internal events, including those generated by incoming messages, as well as requests from node.get
node.on.data catches node.say and node.set incoming messages with a given label

Examples

To learn how to exchange data, let us assume the following use case:

  • a player makes a decision by typing a numeric value into an input form,
  • the value is validated and sent to the server,
  • the server manipulates the value and stores it to compute the final payoff of the player,
  • the server sends the final payoffs to all players.

Note! As explained in the client types page, the client-side code goes in file player.js, and the server-side code goes in file logic.js.

Example 1: node.say and node.on.data

Client-Side (player.js).

  1. Get the input from user, validate it, and show error if not valid.
// Assuming a user typed a numeric value into an input form.
var value = W.getElementById('myFormId').value;
// Parse the input and validate its range, say 0-10.
var parsedValue = JSUS.isInt(value, 0, 10); // See also JSUS.isNumber.
if (false === parsedValue) {
    alert('Invalid value: ' + value);
    return;
}
  1. Send the validated input to the server using node.say.
// Send the value to the server.
node.say('USER_INPUT', 'SERVER', parsedValue);

Explanation

node.say takes the following input parameters:

  • a label (USER_INPUT),
  • a recipient (SERVER),
  • an optional data value (parsedValue).

Server-Side (logic.js).

  1. Declare an event-listener on incoming messages with label "USER_INPUT"; the listener manipulates the incoming data and saves it into the channel's registry.
function storeData(msg) {
    var parsedValue = msg.data;
    // Important! Re-validate the incoming data if sensitive (for example,
    // it is used to compute payoffs).
    // Manipulate the input (e.g. exchange from input to a monetary value).
    parsedValue *= 1.5;
    // Store the value in the registry under the ID of the player (msg.from).
    var userData = channel.registry.getClient(msg.from);
    // Update/create the win variable.
    userData.win = userData.win ? (userData.win + parsedValue) : parsedValue;
}

node.on.data('USER_INPUT', storeData);
  1. At the end of the game, all connected players are notified of their total winnings.
// Loop through all connected players.
node.game.pl.each(function(player) {
    // Get the value saved in the registry and send it.
    var cumulativeWin = channel.registry.getClient(player.id).win;
    node.say('WIN', player.id, cumulativeWin);
});

Client-Side (player.js).

  1. Display the total amount won by the user.
// Listen to the WIN message.
node.on.data('WIN', function(msg) {
    // Write inside the element with ID 'win' the amount of money won.
    W.setInnerHTML('win', 'You won: ' + msg.data);
});

Example 2: node.set

If a player takes multiple decisions within the same step (e.g., in a real-time game), node.set can be used to record each of these.

Replace step 2. of Example 1. with:

node.set({ USER_INPUT: value});

Explanation

node.set takes the following input parameters:

  • the value to set, it can be an object or a string; if a string, it will be transformed into an object containing a key with the name of the string set to true ({ USER_INPUT: value}),
  • the recipient (default SERVER),
  • (Optional) The text property of the message (if set, you can define node.on.data listeners).

Example including optional input parameters:

node.set({ USER_INPUT: value}, 'SERVER', 'decision');

Important! The content of each set message is added to the database, there is no check for duplicates.

Example 3: node.done

If there are no more actions to be taken in a step, the step is over and the game should advance. The end-of-step command is node.done, and it can also be used to send data to server (as in node.set).

Compared to Example 1., sending data with node.done has the advantage of automatically storing the data in the memory database with timestamps and duration; moreover, it makes it easier to save all results at the end of the game (see point 6 below).

Client-Side (player.js).

Replace step 2. of Example 1. with:

node.done({ USER_INPUT: value});

Explanation

node.done takes one input parameter, which is sent to the server with a call to node.set.

Server-Side (logic.js).

To listen to items inserted in the memory database, one can implement the 'insert' listener. For instance, the following code can replace step 3. of Example 1:

node.game.memory.on('insert', function(item) {
    if ('string' === typeof item.USER_INPUT) {
        // Save data to registry, the same as in Example 1.
        storeData({ data: item.USER_INPUT });
    }
});
  1. Saves all results in the data/ folder of the game.
node.game.memory.save('myresults.json');

Example 4: node.get

To invoke a specific function on a remote client and to asynchronously receive back the results of the call, use node.get. The example below executes server-side validation of the input value.

Client-Side (player.js).

var parsedValue = 1234; // Some user-defined value.
node.get('validateInput', function(data) {
    // If server validates input call node.done().
    if (data.valid) {
        node.done({ USER_INPUT: parsedValue });
    }
},
'SERVER',
{
    data: parsedValue
});

Explanation:

node.get gets the following input parameters:

  • The label of the GET message ('validateInput'),
  • The callback function that handles the return message,
  • (Optional) The recipient of the msg (default: 'SERVER'),
  • (Optional) An object with extra options:
    • timeout: The number of milliseconds after which the return listener will be removed,
    • timeoutCb: A callback function to call on timeout (no reply received),
    • executeOnce: TRUE if listener should be removed after one execution,
    • data: The data field of the GET msg,
    • target Override the default DATA target of the GET msg.

Server-Side (logic.js).

node.on('get.validateInput', function(msg) {
    // Validate data.
    var valid = true;
    if (!J.isInt(msg.data)) valid = false;
    return { valid : valid };
});

Note: the server must implement a synchronous reply to the incoming GET message.

A note on the timing of messages

Messages can be exchanged at any point in the game, but ideally inside the step-callback functions to avoid timing issues with event listeners registered in the same step. For instance:

  • This should be avoided:
// Logic.js
stager.extendStep('step1', {
    init: function() {
        node.say('Hello!', 'PLAYER_ID');
    },
    // ...
});

// Player.js
stager.extendStep('step1', {
    cb: function() {
        node.on.data('Hello!', function(msg) {
            // This listener might be registered after the message arrives.
        });
    },
    // ...
});
  • This is acceptable, because the Hello! msg is sent in the step callback.
// Logic.js
stager.extendStep('step1', {
    cb: function() {
        node.say('Hello!', 'PLAYER_ID');
    },
    // ...
});

// Player.js
stager.extendStep('step1', {
    cb: function() {
        node.on.data('Hello!', function(msg) {
            // This listener is registered on time.
        });
    },
    // ...
});
  • This is also acceptable, because the Hello! listener is already registered before step1.
// Logic.js
stager.extendStep('step1', {
    init: function() {
        node.say('Hello!', 'PLAYER_ID');
    },
    // ...
});

// Player.js
stager.setOnInit(function() {
    node.on.data('Hello!', function(msg) {
        // This listener is registered for the entire game.
    });
});

Next Topics

Clone this wiki locally