How To Cog - Chapter 3.2 - Communication

Whenever you create a new cog and add it to a patch, it will be completely seperate from other cogs. There's no mistake you can make that will cause an error in another cog. But when you need your cog to work with other cogs, you're going to need some way to communicate.

Cog provides several ways to do this, but no matter what method you choose, using several cogs to do something makes debugging and reading your code much harder. If you've read some of Raynar's Rbots code, then you know how much more complicated your code can become when you have to break up one cog into several smaller ones. So write plenty of comments beside verbs like SendMessage() and SendTrigger().

Sending Triggers

Triggers are the hardest, most professional, and perhaps the most taxing way to get cogs to talk. A trigger is sent out with SendTrigger() and received with the trigger message. The syntax is:

  SendTrigger(dest, ID, param0, param1, param2, param3); 

The destination is who the trigger is going to be sent to. This integer is either going to be -1 for everybody, or the thing number of the player who's computer you need to talk to. There's more on this in the Multiplayer section.

If a trigger is sent out with a dest of -1, then every cog on all computers in the game will be able to receive that message. Triggers wouldn't be that useful if you couldn't tell them apart. That's what the second argument is for. This is the trigger's ID, which will be the source of the trigger message.

The fact that all cogs that use triggers have to listen to every trigger makes them somewhat wasteful. When you use a trigger and unique ID, every cog listening for triggers on the receiving computer will have to handle that message and compare its ID to the IDs that it's looking for.

Also, because triggers have only a player as a destination and not a cog, it can be difficult for anyone reading your code (and for you later on) to tell which cog is listening for that trigger without any comments.

Say you're writing a new scoring system and you need to write something that will make the server-side cog send a trigger to the client-side cogs to tell them what the new score is. The server code might look like:

broadcast_score:
	// tell all clients that the new score is curScore.
	// receiving cog will be client_score.cog.
	SendTrigger(-1, 33, curScore, oldScore, 0, 0);

return;

(AASN, broadcast_score is a custom message that is run using the call keyword)

And in the client's cog we would have:

trigger:
	if(GetSourceRef() == 33)
	{
		// received trigger from server_score.cog.
		// first param will be the new score, and the
		// second will be the old score.
		curScore = GetParam(0);
	}

return;

In cases like this, where there is an unknown number of recipients, verbs like SendMessage() are not helpful because they are designed to send a message to a specific cog. And they can't send messages to other computers. SendTrigger() is the only choice even though only one cog needed to receive the trigger.

Sending Messages

The two verbs used to send messages are SendMessage() and SendMessageEx(). The syntax looks like:

  SendMessage(cog, message); 
  retval=SendMessageEx(cog, message, param0, param1, param2, param3); 

SendMessage() is the simple version, it just needs the destination cog, and the message to send. SendMessageEx() is the extended version which allows you to send four parameters and receive a value.

An ID is not used when sending messages, but you have a range of user messages (user0-user7) to use instead. There's also a global0 message you can use if you need to. You aren't restricted to using only these messages, you can use any of JK's predefined messages.

The downside of these message verbs is that it's hard to get a reference to a cog - it's not the cog file, it's the number of the cog in the JKL's cog section. A common way to add custom cogs to a patch is through the items.dat. This avoids using the static.jkl, and it allows you to use GetInvCog() to return a reference to that cog. So if you load all of your cogs through the inventory, then you won't have a problem sending messages between them.

Say you have two custom cogs. The first one needs to synchronize three variables with the second. If we used a trigger in this case, then every local cog would hear the trigger, and we don't need to do that. So the first cog might have:

comm_sync:
	SendMessageEx(GetInvCog(GetLocalPlayerThing(), 117), user0, syncVar1, syncVar2, syncVar3, 0);

return;

We're assuming that the first cog is loaded with bin 116, and the second is loaded with 117. The second cog would have this code:

user0:
	syncVar1 = GetParam(0);
	syncVar2 = GetParam(1);
	syncVar3 = GetParam(2);

return;

Using the Inventory

In that last example, we could have used bins 118 to 120 to hold the three variables. It would depend on whether we needed the second cog to act immediately on the change in variables or not. Bins can act as global variables - variables that every cog can modify without making a local copy. This would allow both cogs to change and use the variables as they needed without talking to each other. If you have bins to waste, this might be a better option.

Message Parameters

These parameters are made to accomodate most variable types, but vectors, being as large as they are, have to be broken down into three flexes before you can send them. If you've got a few vectors you need to send (say you're trying to sync something's position and movement), then you're going to have to use multiple triggers. Most of the time it's better if you find another solution than to try that.

With SendMessageEx() you can send a value back to the calling thread with ReturnEx(). The type of the value you return is the same as the message parameters.


Previous: Introduction Up: How To Cog Next: Encapsulation
  • Create:
This page was last modified 16:17, 14 April 2006.   This page has been accessed 1,879 times.