User Messages
From GMod Wiki
Lua: User Messages |
Description: | How to use usermessages |
Original Author: | Nevec |
Created: | August 23, 2010 |
Contents |
Introduction
Client side and server side are two separate realms. You can't simply call a function on the server and have it execute on the client as well, or set a variable on the client and have it update on the server. Usermessages allow you to send data and trigger functions on one or all clients from the server.
The system works like this:
- Call umsg.Start on the server with a unique identifier as the first argument.
- Push data in the message using other umsg.* functions, such as umsg.Char, umsg.Short, etc.
- Call umsg.End(). This will actually send the usermessage.
- On the client, define and hook a callback function that will be called when the usermessage is received.
This article assumes that you know the basics of GMod-specific Lua.
Size limit
Usermessages, unfortunately, have a maximum size of 255 bytes. If this limit is exceeded, the usermessage will not be sent. The most common method of exceeding the limit is sending strings. Methods of how to overcome this limit are explained later.
Every type of value has a size:
- Char and Bool are both 1 byte long.
- Short is 2 bytes.
- Float and Long are 4 bytes.
- Vector and Angle both are 12 bytes long.
- Entity is either 2 or 4 bytes long.
- A string's length is determined by the number of characters + 1.
Length of the message's identifier is also added to the size + 1.
Examples
Simple Usermessage
Server side code:
// start a usermessage with the unique identifier "MyUsermessage" // this usermessage will be sent to every client on the server as soon as this code is run umsg.Start( "MyUsermessage" ); // send the usermessage umsg.End();
Client side code:
// define a callback function that will be called when the usermessage is received local function RecvMyUmsg() // print something to the console print( "MyUsermessage was received!" ); end // hook the callback function to the usermessage // when the client receives "MyUsermessage", RecvMyUmsg will be called usermessage.Hook( "MyUsermessage", RecvMyUmsg );
Usermessage with data
Lets expand on the previous example.
Serverside code:
umsg.Start( "MyUsermessage" ); umsg.String( "My string" ); umsg.Float( 123.456 ); umsg.Long( 123456 ); umsg.Bool( false ); umsg.End();
Clientside code:
// a single argument gets passed to the callback function: a bf_read object that contains the data you sent // the object will still be there if you send an empty usermessage local function RecvMyUmsg( data ) print( "String: "..data:ReadString() ); print( "Float: "..data:ReadFloat() ); print( "Long: "..data:ReadLong() ); print( "Boolean: "..data:ReadBool() ); end usermessage.Hook( "MyUsermessage", RecvMyUmsg );
Note: You need to read data in the same order as you send it. If you sent a string first, read it first, etc.
Note: Usermessages are sent as soon as umsg.End() is called, if the code is located in the body of a file, it will be sent as soon as the file has loaded, often before the client's lua has loaded, resulting in him not receiving the message. Send usermessages primarily when hooks are called, or using timers to make sure there is an audience for the message. Send data the client needs immediately upon joining in a PlayerInitialSpawn hook for example.
Practical example
You are making a server administration addon, for instance, and you want the menu to have a list of maps that are available on the server. Since your map list and the server's may differ, you need to manually send the map names.
The server can have hundreds of maps, in which case the whole list may not fit in a single message. This is a good place to use datastream, but, for the sake of this article, we will use plain usermessages.
Server:
local function PlayerInitialSpawn( pl ) // find every map for i, mapName in ipairs( file.Find( "../maps/*.bsp" ) ) do // send a usermessage for every map umsg.Start( "AddMap", pl ); umsg.String( mapName ); umsg.End(); end end hook.Add( "PlayerInitialSpawn", "SendMapList", PlayerInitialSpawn );
Client:
// define a table that will keep all of the maps local MapList = {}; // define a callback function for the usermessage local function AddMap( data ) // insert the received map name into the table of maps table.insert( MapList, data:ReadString() ); end usermessage.Hook( "AddMap", AddMap );
Recipient Filter
The second argument for umsg.Start is a recipient filter object. If you want to send the usermessage to every player on the server don't supply this argument at all. If you want to send it to a single player, supply the player object instead. If you want to send it to specific players, use the RecipientFilter object.
local filter = RecipientFilter(); filter:AddPlayer( pl1 ); filter:AddPlayer( pl3 ); umsg.Start( id, filter ); umsg.End();
Client to Server
There is no way to send usermessages from client to server. You have to use concommands for this. Console commands are essentially the same as usermessages, except they send the data in string form, whereas usermessages send the data in binary form. The long number 1238419 will take up only 4 bytes when sent in a umsg, but 7 bytes when sent in a concommand.
Concommands have the same limit of 255 bytes.
Datastream
Datastream is essentially a wrapper for concommands and usermessages that allows you to send complex tables and exceed the size limit. It does this by serializing the input before sending and deserializing after receiving. Once the data is serialized into a string, it is sent using multiple usermessages or console commands. Datastream can also be used for sending data from client to server.
You should only use datastream when sending complex tables or very long strings. Datastream makes sending complex structures very easy, but it sacrifices performance.
See Also
- usermessage Library
- umsg Library
- RecipientFilters
- bf_read Object