#Socket Architecture

1 messages · Page 1 of 1 (latest)

native drum
upper obsidian
native drum
#

I just tried to replicate your example at the beginning of the thread before I do something on my own.

upper obsidian
native drum
#

no, the emit works and socket.on('module.mymod', Mymod.handler) picks it up. but I can't seem to get the acknowledge to work. so the promise of the requester never resolves.

upper obsidian
#

aah, I see the problem

#

since the time that my initial message in this channel was posted the ordering of arguments for the socket request changed. They are now: (eventName, requestData, options, acknowledgment)

#

so you need an options object, even if it is undefined or empty

#

so try:

game.socket.emit("module.mymod", {foo: "bar"}, {}, response => console.log(`Received acknowledgment`));
native drum
#

thanks, this triggers the callback.
but since handleCustomSocket on the server side does not pass any parameters, like the message, to the callback one cannot use it this way to do something with with the message and return a result to the requestor.
i.e. response is undefined in the above example
or am I missing something?

upper obsidian
#

We could change the API to send you back your own data as a reminder, but you already know what was sent so you could, for example, bind that data object to your handler function

errant solar
#

cc @spiral notch (making a thread to make this easier to follow)

#

@upper obsidian
Does the acknowledgement callback here get the same arguments that every other connected client's socket.on callback would get?
Or is response here a simple object with like a status: "ok" inside it?

upper obsidian
#

the acknowledgement contains the same response that other clients would receive through the broadcast - all clients react to the response in the same way, but the differentiation between acknowledgement and broadcast allows for the requesting client to understand when the workflow is complete (in the enclosed promise)

errant solar
#

awesome, thanks

#

So this would be an example with multiple arguments being sent over the socket emission if I understand rightly.

function ackCb(arg1, arg2, arg3) {
  console.log(arg1, arg2, arg3); // expected: "foo bar baz"
};

socket.emit('module.my-module', 'foo', 'bar', 'baz', ackCb);
spiral notch
#

@upper obsidian I think I need to drop some contextual code so I can relate my use case and ensure I have it straight in my head.

#

The scenario here is processing damage when an actor is Concentrating on a spell within the 5e system

#

a GM user is nominated to process the damage and then distribute prompts to any owners of the damaged actor if they are concentrating. That leads to here:

#
/**
     * Distribute concentration prompts to affected users
     * @param {*} actorId 
     * @param {*} users 
     */
    static _distributePrompts(actorId, userIds, dc){
        if (!actorId || !userIds || !userIds?.length) return;

        if (userIds.includes(game.userId)) {
            Concentrator._displayPrompt(actorId, game.userId, dc);
            const thisUserIndex = userIds.indexOf(game.userId);
            userIds.splice(thisUserIndex, 1);
        }

        game.socket.emit(`module.${NAME}`, {
            gadget: GADGETS.concentrator.name,
            action: "prompt",
            targetUserIds: userIds,
            actorId,
            dc
        });
    }
#

previously I just had the socket emission here but the emitting client was not seeming to get the broadcast in the socket listener here:

/**
     * Socket dispatcher
     * @param {*} message 
     */
    static _onSocket(message) {
        if (!message?.gadget) return;

        switch (message.gadget) {
            case BUTLER.GADGETS.concentrator.name:
                Concentrator._onSocket(message);
                break;
        
            default:
                break;
        }
    }

/**
     * Socket message handler
     * @param {*} message 
     */
    static _onSocket(message) {
        if (!message?.targetUserIds || !message?.targetUserIds?.includes(game.userId) || !message?.action) return;

        switch (message.action) {
            case "prompt":
                if (!message.actorId) return;
                Concentrator._displayPrompt(message.actorId, game.userId, message.dc);
                break;
            
            case "cancelOtherPrompts":
                if (!message.actorId) return;
                Concentrator._cancelPrompt(message.actorId, message.userId);
                break;
        
            default:
                break;
        }
    }
```\
#

if I wanted the emitting client to also possibly receive a prompt, what is the correct way to handle the acknowledgement and process the emission as if that client were also receiving the broadcast, without simply adding non-socket code in the method as I have done?

errant solar
spiral notch
upper obsidian
#

For bonus points, this allows you to wrap your function in a promise to make the whole thing async, for example

async function requestConcentrationCheck(userIds, actorId, dc) {
  return new Promise(resolve => {
    const requestData = {
      gadget: GADGETS.concentrator.name,
      action: "prompt",
      targetUserIds: userIds,
      actorId,
      dc
    };
    game.socket.emit(`module.${NAME}`, requestData, responseData => {
      performConcentrationCheck(responseData); // you could in theory be requesting yourself to perform a check
      resolve();
    });
  });
}```
Where presumably you have some other function where clients respond to concentration check requests like:
```js
function performConcentrationCheck(checkData) {
}
spiral notch
#

thanks that will make life easier

#

from what small part i understand, the Hooks class sits on socket.io, so was there a thought to make game.socket part of the Hooks ?

#

for me it would be more intuitive to call Hooks.call("module.name", message) and then Hooks.on("module.name")

#

but maybe i misunderstood

upper obsidian
#

hooks are not networked in any way

#

hooks are called and handled all locally

#

like DOM events

spiral notch
#

so for say a document update, the call to the db and the callout for hooks are essentially separate?

#

I could probably answer this myself looking at the code, I'm sure I've looked at 100s of times without really parsing it

upper obsidian
#

well, they aren't separate, but the hooks are called (locally) as part of that workflow

#

See ClientDatabaseBackend#_createDocuments for a good example

#

in the _preCreateDocumentArray method we dispatch the preCreate{type} hook

#

this occurs only for the requesting client

#

and then in _handleCreateDocuments we dispatch the create{type} hook which occurs for all clients

spiral notch
#

yeah that makes sense I think (can't look at the code right now but it tracks in my head)

spiral notch
#

I don't know what I'm doing wrong but the acknowledgement callback never seems to fire

/**
     * Distribute concentration prompts to affected users
     * @param {*} actorId 
     * @param {*} users 
     */
    static async _distributePrompts(actorId, userIds, dc){
        if (!actorId || !userIds || !userIds?.length) return;

        // if (userIds.includes(game.userId)) {
        //     Concentrator._displayPrompt(actorId, game.userId, dc);
        //     const thisUserIndex = userIds.indexOf(game.userId);
        //     userIds.splice(thisUserIndex, 1);
        // }
        return new Promise((resolve) => {
            const requestData = {
                gadget: GADGETS.concentrator.name,
                action: "prompt",
                targetUserIds: userIds,
                actorId,
                dc
            };

            game.socket.emit(`module.${NAME}`, requestData, (responseData) => {
                // this never appears to be reached
                Signal._onSocket(responseData);
                resolve();
            });
        });
    }
spiral notch
#

@upper obsidian coming back to this, I can't figure out why my socket emit is firing but the ack cb is not, any pointers of where I should dig first?

upper obsidian
#

im not seeing the acknowledgment being reflected back for a custom socket event (which are the ones that modules have access to)

spiral notch
#

i just left my dinky workaround in place for now and i'll make it smarter after a future update thanks!