You are on page 1of 17

Networking Tutorial: GameMaker Studio

As this tutorial is very GameMaker oriented I will not be going deep into the technological side
of networking. This is a tutorial for "intermediate" GameMaker users.
The actual tutorial starts at section "Server Tutorial: Part 1" and goes on from there. All the
information before the tutorial is extra, but valuable information!
-------------------------------------------------------------------------------------------------------------------- TCP / IP / Server-To-Client Model / Client-To-Client Model -What is TCP? --> http://en.wikipedia.org/wiki/Transmission_Control_Protocol
What is IP? --> http://en.wikipedia.org/wiki/IP_address
In short TCP/IP works by exchanging packets of information(in the form of bytes) between IP
Addresses. These packets of information can be sent / received and used to perform whatever sort
of application you might need.
What is UDP? --> http://en.wikipedia.org/wiki/User_Datagram_Protocol
NOTE: This tutorial does not use nor focuses on UDP.
I won't go into UDP, but UDP is similar to TCP, but it has a problem as well as an upside. The
upside is UDP is faster than TCP. The downside is that UDP has the possibility of losing packets
because UDP is unreliable. UDP is usually used to send unimportant small bits of information,
such as retrieving / sending ping. UDP is also connectionless, meaning you don't have a direction
connection to anyone or anything with UDP.
TCP works by setting up a host(server) and having a client connect to the host in order to
properly exchange information. This can be used multiple ways though, one being Server-ToClient and the other Client-To-Client models.
What is a Server? A server is a host application that stores data and passes all data through itself
and sends it to a single client or multiple clients, whichever is necessary.
What is a Client? A client is an application that sends / receives requests from the server it is
connected to in order to exchange data with the server.
Server-To-Client: The server to client method works by having clients connect to a host(server)
and have the clients send data to and make requests for data from the host(server). This method is
more secure than direct client-to-client methods. The reason for this being that each client is not
directly connected to each other, but connected to a server instead to act as a medium for data
exchange.
Client-To-Client: The client to client method works by having a client host a server ontop of a
client and have a client or multiple clients connect directly to the host client. This is less secure
than the server-to-client method since clients are directly connected to each other. The reason this
is unsafe is that harmful clients can take other clients information and use it without the affected

client's permission.
Local Host / Play: (Windows OS Related Only) Local play does not require any extra work
besides setting up your game and running the server / client on computers connected to your own
router. However if you have Windows Firewall enabled, you'll either need to disable Windows
Firewall or allow your program to bypass Windows Firewall. To allow your program through
Windows Firewall: Go To Control Panel -> System and Security -> Windows Firewall -> Allow a
program or feature through Windows Firewall. Finally, click Change Settings and Allow a
Program and find your program. Then hit OK and you're done.
Global Host: Hosting a server for global play---unlike local host---requires port forwarding. I
recommend doing "Port-Range Forwarding" where you portforward your IP address on a range of
ports. This will allow you to change your port without having to worry about doing another portforward for your new port.
IPv4 Address Problem: If you're hosting on Windows(not sure about other devices) you may
notice your IP address change from time to time. This means your device's IP address is dynamic,
meaning it changes. This will in turn make your server's host IP address change as well. To avoid
this, what you can do is set your IPv4 Address to a "static IP address". This will keep your IPv4
address from changing and this will keep you from having to port-forward again each time your
IPv4 Address changes.
Room Speed: Game Maker Studio processes everything in "steps" which is the cap on the FPS
called "room speed". If your room speed is 30, your game runs on average at 30 FPS or 30
steps/frames per second. This limits the number of packets of data your game can process over a
network to: X number of messages per 30 steps/frames. So if you increase your room speed, you
can increase the number of messages your game's network can process! Example, if your game
runs at 500 room speed, your game's network can process up to X number of messages per step at
500 steps per second! See the increase? However having such a high room speed, means you ned
to look into delta time: http://en.wikipedia.org/wiki/Delta_timing
------------------------------------------------------------------------------------------------------------------Setup: Simple Server / Client Application / Important Information
The rest of this tutorials assumes you already know how to program in Game Maker Studio.
Please do not attempt to create a networking application without first knowing how to program or
how to program in Game Maker Studio.
-- Start of Bonus Info Questions and Answers:

What are sockets? Sockets are "identifiers" for clients, each socket points to a specific
client and each client is given a socket when it connects to the server. Sockets are used to
send data to the clients they are related to to.

* However sockets aren't only for clients on the server. The server and client each
have a socket in their own application. The server's socket on the server application is
created and returned using "network_create_server()". The client's socket on the client
application
is created and returned using "network_create_socket()".

What are buffers? In short, buffers are a form of data storage. You write data to buffers
and read data from buffers. You also read data the same way you write the data. So if you
wrote A, B, C in that order, then you'd read the data back out as A, B, C in the same order.
Remember though, when writing or reading data from a buffer, make sure that the data
type you're reading/writing matches the value being read from or written to the buffer or
you'll end up reading or writing incorrect values!
When dealing with buffers, we can have two buffers. A buffer received from a packet of
data sent from a client or server, this is the read buffer. Then we have a second buffer
that is used to send data from client to server, or server to client, this is the write buffer.

Why do I need to store these sockets? Sockets don't record/store themselves, if you
don't have the sockets, you can't send data back to the clients the sockets belong to. That
is why we make a data structure to store the sockets in.

What is a packet and what is a message ID: A "packet"(in networking) is a collection


of data written to a buffer. We can write whatever data we want to a packet, however one
thing is always needed for every
packet. That is a "message ID" or msgid for
short. The msgid of a packet helps the server to determine what the packet is for and how
the packet should be processed by the server. The msgid ALWAYS comes before any
other data written to a packet. The msgid is also always read from a packet before every
other set of data. Message IDs can be whatever you want, a string or real value. However,
efficiency is always top priority, so message IDs are usually written to a buffer as a very
small "real" data type. A "real" or real value is simply a "number" which can either be an
integer or float value(float values are numbers with a decimal point, e.g. 4.5).

Async Networking "Type" Events:


In Any Type Event:
"type" : The type of event.
"id" : The socket receiving the type event.
"ip" : The IP address of the socket receiving the type event.
Network Type Connect:
"socket" : The socket of the client that connected.
Network Type Disconnect:
"socket" : The socket of the client that disconnected.

Network Type Data:


"buffer" : The buffer containing the data we received.
"size" : The size of the buffer containing the data we received.

Data Types For Buffers:


Game Maker has several data types that can be written to and read from a buffer. These data types
are listed off and explained below:
Note(s):
The "u", "s" and "f" in the names of the data types stand for: unsigned, signed and float.
The number that comes after the "u", "s" and "f" is the size of the data type in bits.
8 bits = 1 byte. E.g. 8 bits = 1 byte, 16 bits = 2 bytes, 32 bits = 4 bytes, etc.
0 is classified as positive with data types.
bool or boolean is a single byte data type that can be either 0(false) or 1(true).
One character in a string is equal to a single byte.
buffer_u8 : Unsigned integer from 0 to 255.
buffer_u16 : Unsigned integer from 0 to 65,535.
buffer_u32 : Unsigned integer from 0 to 4,294,967,295.
buffer_s8 : Signed integer from -128 to 127.
buffer_s16 : Signed integer from -32,768 to 32,767.
buffer_s32 : Signed integer from -2,147,483,648 to 2,147,483,647.
buffer_f16 : (Not supported!) Float number from -65504 to 65504.
buffer_f32 : Float number from -16777216 to 16777216.
buffer_f64 : Float number from -(2^52) to (2^52) - 1.
-(2^52) = -4,503,599,627,370,496
(2^52) - 1 = 4,503,599,627,370,495;
buffer_bool : A boolean value, a value can only be 0(false) or 1(true).
buffer_string : A string value, size in bytes depend on the length of the string.

Buffer Types:
buffer_fixed : A buffer that is a fixed size and never changes in size. If any data written to the
buffer of this type would make this buffer's size exceed it's fixed size, data on the end of the
buffer will be removed until the buffer has returned to it's fixed size.
buffer_grow : A buffer that grows as data is added to it. If any data is written to a buffer of this
type makes the buffer's size exceed the original size of the buffer, the size of the buffer increases
to fit the amount of data stored in the buffer. Buffers of this type will remain the same size as they
grow no matter how much data you remove. If your original buffer is size of 1024 and grows to
2048, the buffer's size will remain at size 2048.
buffer_wrap : A buffer that wraps it's data. If any data is written to a buffer of this type that would
make the buffer's size increase, the data would then "wrap" the buffer, meaning it'll insert itself at
the beginning and overwrite any data it overlaps at the beginning of the buffer. This in turn makes

the buffer never increase in size.


buffer_fast : A "stripped" down buffer. This buffer is stripped down, meaning it has little
overhead, making this buffer type extremely fast to read/write to compared to other buffer types.
However only data types of buffer_u8 and buffer_s8 can be written to this type of buffer. If any
data written to the buffer of this type would make this buffer's size exceed it's original size, data
on the end of the buffer will be removed until the buffer has returned to it's original size.
Estentially, buffer_fast is a faster version of buffer_fixed, however limited in the data types it can
hold.
-- End of Bonus Info -

--- Server Tutorial: Part 1 --A few things need to be handled before a server is ready to handle any clients or process any
information from clients. What we need to do is first create the server via code, create a data
structure or array to handle "sockets" that come from connected clients and create a buffer:
//Create Event of Object: ObjServer
var Type , Port , MaxClients;
Type = network_socket_tcp;
Port = 64198;
MaxClients = 32;
Server = network_create_server( Type , Port , MaxClients );
var Size , Type , Alignment;
Size = 1024;
Type = buffer_fixed;
Alignment = 1;
Buffer = buffer_create( Size , Type , Alignment );
SocketList = ds_list_create();
As you can see above, in code, we create a TCP server on port 64198 with a maximum number of
connected clients of 32. We also created a ds_list that will hold our client sockets and a buffer
with the size of 1024 bytes, the type of buffer is "fixed" and the byte alignment is 1.
As an example and test run, we should start a brand new, empty project. In that project create a
new object that will function as our server and give that object a name, e.g. ObjServer. In that
object give it a "create event" and type in your code that creates your server. Like the code above.
Be sure to type the code in and not just copy and paste it from this tutorial. This will help you to
memorize and become familiar with the code.
Now that our server is created we need to check for incoming clients that are trying to connect to
the server, remove clients from the server that disconnected and check for data that is being sent
from clients. This is all done in Game Maker Studio's Async Networking event.

The async_event has a special ds_map ID that holds all incoming information to the server. This
ds_map ID is unique to all the async events, meaning it does not work outside of these events.
The ds_map ID for the event is called "async_load". In the async event, the async_load ds_map
always holds three pieces of information. These pieces of information are:
Key: "type", key: "id" and key: "ip". Async_load is a ds_map and data in ds_maps are found by
searching the ds_maps "keys". The data is stored with these keys and the data can be retrieved via
e.g.: data = ds_map_find_value( map , key ).
The rest of the data stored in the map depends on the current event type of the Networking Event.
This "event type" is basically what happens when some form of data is received from a client.
The event type is in the async_load map as key "type". The event types are located below the
"Questions and Answers" section of this tutorial. This next bit of code is a bit lengthy, but please
bare with it:
//Async Networking Event of Object: ObjServer
var type_event = ds_map_find_value( async_load , "type" );
switch( type_event ) {
case network_type_connect:
var socket = ds_map_find_value( async_load , "socket" );
ds_list_add( SocketList , socket );
break;
case network_type_disconnect:
var socket = ds_map_find_value( async_load , "socket" );
var findsocket = ds_list_find_index( SocketList , socket );
if ( findsocket >= 0 ) {
ds_list_delete( SocketList , findsocket );
}
break;
case network_type_data:
var buffer = ds_map_find_value( async_load , "buffer" );
var socket = ds_map_find_value( async_load , "id" );
buffer_seek( buffer , buffer_seek_start , 0 );
ReceivedPacket( buffer , socket );
break;
}
Looks complicated right? Fear not, it's actually easy to understand! We start by getting the event
type, then we perform a switch statement to check which case(type of event) matches the event
found(event_type).
NOTE: You'll notice that the networking event may be a bit strange because of one thing
that happens when you're either hosting a server or client. When hosting a client the
async_map's key "id" will actually return the "TCP" or "UDP" socket the data was

received from. However, when hosting a server the async_map's key "id" will return the
socket of the client that sent the data. Be sure to remember this since the GM:S help file
does NOT tell you this!
If the switch statement returns type "network_type_connect", then we go ahead and add the
client's socket to the "SocketList" to record for later use. If the switch statement returns type
"network_type_disconnect", then we check to see if the client's socket is in the "SocketList", if it
is in the list, we delete it from the list. If the switch statement returns type "network_type_data",
then we get the buffer and the socket ID of the client that sent the data, set the reading position of
the buffer to the start. Then pass the buffer and socket to the ReceivedPacket script. I will explain
what "reading position" is later on.
Now that the above piece of code is handled, we need to determine what happens to our
data/packet in the buffer we received in the "network_type_data" event type. First we know that
we passed the buffer containing our data to a script "ReceivedPacket". So if we have not already
created the script, we need to create a new script and name it "ReceivedPacket". This script
decides what we are going to do with the data/packet. Which brings us to the next piece of code
that goes in the ReceivedPacket script:
//Server Script : ReceivedPacket
var buffer = argument[ 0 ];
var socket = argument[ 1 ];
var msgid = buffer_read( buffer , buffer_u8 );
switch( msgid ) {
//Case statements go here...
}
Here you see we are getting the buffer passed to the script, getting the message ID(msgid) of the
packet and we're using a switch statement to determine what happens based on the value of the
message ID. Of course though, we don't know what data we will be receiving from the client yet,
so the switch statement is empty.
Next we add a "game end" event to our server object. Here we want to make sure to delete all
dynamic data used by the server. Our code's dynamic data currently includes the server socket,
buffer and ds_list. So we want to make sure to delete them:
//Game End Event of Object: ObjServer
network_destroy( Server );
buffer_delete( Buffer );
ds_list_destroy( SocketList );
Extra Info: Now you probably want to do a few small things, such as simply the number of
connected clients and check to see if the server was created successfully. You can do this like so:
draw_text( 5 , 5 , "Server Status: " + string( Server >= 0 ) );

draw_text( 5 , 20 , "Total Clients: " + string( ds_list_size( SocketList ) ) );

--- Client Tutorial: Part 1 --Now that the server is taken care of, lets focus on the client and what needs to be done for it to
connect to the server and receive data from the server.
First we need a client socket and a code to connect that client socket to a server. Of course you
can only connect to a server if it is online(but that's implied right?). Next we need a buffer to send
our data/packets through for when we send messages to the server:
//Create Event of Object: ObjClient
var Type , IPAddress , Port;
Type = network_socket_tcp;
IPAddress = "127.0.0.1";
Port = 64198;
Socket = network_create_socket( Type );
isConnected = network_connect( Socket , IPAddress , Port );
var Size , Type , Alignment;
Size = 1024;
Type = buffer_fixed;
Alignment = 1;
Buffer = buffer_create( Size , Type , Alignment );
This code here will create our client, create buffer to send data/packets on and check to see if the
client actually connected to the server! First we need to get the socket type, in this case TCP, then
the server's IP address, we're using localhost, and then we need the port the server is hosted on.
After that we create our buffer, just like we did on the server.
As an example and test run, we should start a brand new, empty project. In that project create a
new object that will function as our client and give that object a name, e.g. ObjClient. In that
object give it a "create event" and type in your code that creates your client. Like the code above.
Be sure to type the code in and not just copy and paste it from this tutorial. This will help you to
memorize and become familiar with the code.
Now that we have created our client we need to check for any incoming data/packets from the
server. Since clients don't receive connection/disconnection type events, we don't include them in
our client side code for the Async Networking Event:
//Async Networking Event of Object: ObjClient.
var type_event = ds_map_find_value( async_load , "type" );
switch( type_event ) {
case network_type_data:

var buffer = ds_map_find_value( async_load , "buffer" );


buffer_seek( buffer , buffer_seek_start , 0 );
ReceivedPacket( buffer );
break;
}
We start by getting the event type, then we perform a switch statement to check which case(type
of event) matches the event found(event_type), since we're only checking for incoming
data/packets from the server, we only check for one event type: network_type_data. If the switch
statement returns type "network_type_data", then we get the buffer the server sent, set the reading
position of the buffer to the start then, pass the buffer to the ReceivedPacket script. I will explain
what "reading position" is later on.
Now we need to determine what happens to our data/packet in the buffer we received in the
"network_type_data" event type. First we know that we passed the buffer containing our data to a
script "ReceivedPacket". So if we have not already created the script, we need to create a new
script and name it "ReceivedPacket". This script decides what we are going to do with the
data/packet. Which brings us to the next piece of code that goes in the ReceivedPacket script:
//Client Script : ReceivedPacket
var buffer = argument[ 0 ];
var msgid = buffer_read( buffer , buffer_u8 );
switch( msgid ) {
//Case statements go here...
}
Here you see we are getting the buffer passed to the script, getting the message ID(msgid) of the
packet and we're using a switch statement to determine what happens based on the value of the
message ID. Of course though, we don't know what data we will be receiving from the server yet,
so the switch statement is empty.
Finally we add the "game end" event to our client object. Here we want to make sure to delete all
dynamic data used by the client. Our code's dynamic data currently includes the client socket,
buffer and ds_list. So we want to make sure to delete them:
//Game End Event of Object: Client
network_destroy( Socket );
buffer_delete( Buffer );
Extra Info: Now you probably want to do a few small things, such as display if the client is
connected to the server or not:
draw_text( 5 , 5 , "Client Connected: " + string( isConnected ) );

--- Server & Client Tutorial: Part 2 --You've gotten this far, might as well finish the tutorial! So far we have covered setting up a
simple client and server. This includes the server being able to check for connecting /
disconnecting clients and receiving data from clients. It also includes the client being able to
connect to a server and be able to receive data from a server. However we're still missing one
piece of crucial information! Sending data/packets as well as identifying data.packets we retrieve
in our ReceivedPacket script on the server and client.
First we know that data/packets are written to buffers. Thus, we'll take a short walk through
buffers and how to set them up as packets for networking.
As networking needs to be both efficient and concise to support a game or application, we need to
also keep our buffers efficient and concise. This means that we should only write data that is
*completely necessary* to the buffer.
For this next part, we'll be setting up a simple but effective *pinging* example using the code we
created in the tutorial. If you didn't know, pinging(in networking) is a process by which we check
the validity or strength of a connection between two applications, such as a server and client.
You can do two simple things with data in a buffer, read data from a buffer or write data to a
buffer. The "reading position" as mentioned earlier, is the position in the buffer we want to start
reading data from. The "writing position" is the position in the buffer we want to start writing
data to the buffer.
For networking(with games) what we want to do is always start reading / writing data from the
beginning of the buffer, this makes using buffers very easy to handle as the process is very linear.
So first off, we want to set the writing position to the start of the buffer, then simply write our
data to the buffer. On that note, lets write our first packet that our client will send to the server in
order to request a ping of the server:
//Step Event of Object: ObjClient
buffer_seek( Buffer , buffer_seek_start , 0 );
buffer_write( Buffer , buffer_u8 , 1 );
buffer_write( Buffer , buffer_u32 , current_time );
var Result = network_send_packet( Socket , Buffer , buffer_tell( Buffer ) );
Despite the code being short, we did a LOT right here. First we set the writing position of the
buffer to the start, we wrote our packet's "message ID"(as mentioned earlier) to the buffer and
then wrote the current time to the buffer. Then finally, we sent the buffer/packet of data to the
server from the client and got the result. Getting the result of the send allows us to check if we're
still connected to the server! If the result is greater than or equal to zero, then the buffer/packet of
data was successfully sent, else the send failed. "Socket" is the socket that we're connected to and
where we're sending the buffer. "Buffer" is the buffer containing our packet.
"buffer_tell( Buffer )" is the size in bytes of the packet of data we wrote to the buffer.
At this point, if you haven't started wondering already, I'll bring the question up for you: "Why do
we not write the code in the Async Networking Event?" with which I answer: The Async
Networking Event is not optimal for sending data unless, we're sending data right after we've
received data. The reason being, the Async Networking Event is only active when either a client
connects to a server, a client disconnects from a server or a server or client is receiving data.
When none of these cases occur, we wouldn't be able to send data, thus we send our data in the
"Step Event" since the data is independant of reading data from a buffer.
Now you know how to change the writing position of the buffer, write data to the buffer and how
to send data between server and client. So lets get into reading from a buffer. This is relatively

simple, we set the reading position(same way as writing) to the start of the buffer and read our
data from it. Since earlier, we sent a packet, to the server, we'll retrieve it in our ReceivedPacket
script:
//Server Script : ReceivedPacket
var buffer = argument[ 0 ];
var socket = argument[ 1 ];
var msgid = buffer_read( buffer , buffer_u8 );
switch( msgid ) {
case 1:
var time = buffer_read( buffer , buffer_u32 );
buffer_seek( Buffer , buffer_seek_start , 0 );
buffer_write( Buffer , buffer_u8 , 1 );
buffer_write( Buffer , buffer_u32 , time );
network_send_packet( socket , Buffer , buffer_tell( Buffer ) );
break;
}
As you'll notice we update the Server's ReceivedPacket script with our new code. What we did
was retrieve the buffer, read our message ID from the buffer and determined what we should do
depending on the message ID. In this case our message ID is 1, so we perform case 1. In case 1
we read the current time out of the read buffer, write our message ID to the write buffer and write
the current time into the write buffer and send the packet of data to the client via "socket".
Then do the same process for the client, but we won't be sending anything back to the server this
time. We'll be doing *pinging* which is calculating the strength of the connection between client
and server:
//Client Script : ReceivedPacket
var buffer = argument[ 0 ];
var msgid = buffer_read( buffer , buffer_u8 );
switch( msgid ) {
case 1:
var time = buffer_read( buffer , buffer_u32 );
var Ping = current_time - time;
break;
}
So now you'll notice, that, Ping = Current Time - Previous Time. So Ping is the time it takes for
the client to send a packet to a server and for the server to send a packet back to the client!
With this, we have covered all bases! We know how to set up a client and server. How to check
for connections / disconnects of clients on a server and how to send / receive data between server
and client.
Extra Info: If you'd like to display data such as the "Result" or "Ping" you'll need to initiate their

variables in the ObjClient's create event and remove "var" from before the names of both
variables. You can then display them on screen:
draw_text( 5 , 5 , "Connected: " + string( Result >= 0 ) );
draw_text( 5 , 20 , "My Ping: " + string( Ping ) );
Remember though, previously we had "isConnected" which was our original way to check if we
connected to the server once the client connected. However, "Result" updates if we're *still*
connected or not. So keep this in mind since both are not needed.
---------------------------------------------------End of Tutorial----------------------------------------------------------------------------------------------Buffer Explanation------------------------------------------NOTE: Data types have been included at the top of the tutorial under section -- Start of Bonus
Info -- in Data Types For Buffers.
As you will notice networking is completely dependant upon buffers. Buffers are needed in order
to both send data and receive data; so it's necessary that we have a good understanding of buffers!
Lets recap what buffers do and how they're used in networking: Buffers are forms of data storage
that can be used in plenty of different ways. You can use buffers to store data, send or receive data
over a network or even for encoding game save data or decoding game load data.
In order to used buffers we need to know their functions, so lets go over a few very useful ones.
The first being: buffer_create( size , type , align ). This functions will allocate(apply) dynamic
memory for the buffers use, the amount of memory allocated depends on the "size of the buffer,
once the buffer is created the function returns the buffers ID. A buffer needs a type, there are
multiple types of buffers you can create: buffer_fixed, buffer_grow, buffer_fast and buffer_wrap;
these buffer types have been explained in the -- Start of Bonus Info -- in Buffer Types
section of the top of the tutorial. It would be advantagous to look over each buffer type so you can
get a better understanding of how each type works. Finally we have align or byte alignment.
This is a tricky subject...
Now networking isnt exaclty dependant upon having a byte alginment higher than 1. In turn this
means you dont need to learn about byte alignment unless youre interested. If you arei nterested
you can look it up here: http://gmc.yoyogames.com/index.php?showtopic=602321&hl=

So to create a simple buffer, this will create a fixed type buffer with a size of 1024 bytes with a
buffer alignment of 1(example):
Buffer = buffer_create( 1024 , buffer_fixed , 1 );
Now that we have our buffer, we want to know where to start writing data to it. To do this we
have our next function: buffer_seek( buffer , base , offset ). This function takes your buffer
and changes the writing(and reading) position in the buffer. So buffer is the buffer to change
positions of, base is the position to change to and offset is the offset of the intial position in

bytes. base however has three constants you can use: buffer_seek_start, buffer_seek_end and
buffer_seek_relative. buffer_seek_start sets the writing / reading position to the start of the
buffer. buffer_seek_relative sets the writing / reading position relative to the current
writing(and reading) position. buffer_seek_end sets the writing / reading position to the end of
the buffer.
As an example we can set the writing / reading position in our previous buffer to the start of the
buffer like so:(example)
buffer_seek( Buffer , buffer_seek_start , 0 );
We have now set our writing position of our buffer so lets work on writing data to the buffer
using: buffer_write( buffer , type , value ). This function takes your buffer and writes a piece
of data of type type with a value of value to your buffer. After the value is written, the writing
/ reading position in the buffer is advanced by the number of bytes written to the buffer with this
function. This allows you to write multiple values to a buffer one after another without hassle.
For example lets write a buffer_u8(8bit or 1byte) value to our buffer(example):
buffer_seek( Buffer , buffer_seek_start , 0 );
buffer_write( Buffer , buffer_u8 , 16 );
Writing data is easy, now reading data is just as easy using: buffer_read( buffer , type ). This
function reads a piece of data of data type type from your :buffer. After the value is read from
the buffer, the writing / reading position in the buffer is advanced by the number of bytes read
from the buffer with this function.
So lets try reading a value from out buffer, that we wrote to the buffer previously:(example)
buffer_seek( Buffer , buffer_seek_start , 0 );
var value = buffer_read( Buffer , buffer_u8 );
So what if we want to get the size of all the data in the buffer after we have written the data to the
buffer? We use: buffer_tell( buffer ). This function gets the total number of bytes of data written
to the buffer after the writing position. So if we set the writing to the start and write 4 buffer_u8
data types to the buffer, this function we return 4 bytes(4 buffer_u8 types).
In networking we want to only send the amount of data we have written to our buffer from the
current seek position, buffer_tell( buffer ) helps us with this! As the function gets the total amount
of data written after our writing position and we can then relay that to sending our packet via:
network_send_packet( socket , buffer , size ). See the size? That is where buffer_tell( buffer )
would go in order to send only the data we want to send as previously stated.
So lets try getting the total number of bytes written to the buffer(example):
buffer_seek( Buffer , buffer_seek_start , 0 );
buffer_write( Buffer , buffer_u8 , 16 );
buffer_write( Buffer , buffer_u8 , 16 );

buffer_write( Buffer , buffer_u8 , 16 );


buffer_write( Buffer , buffer_u8 , 16 );
var total = buffer_tell( Buffer );
On some occasions you might want to get the size of your entire buffer. You can do this using:
buffer_get_size( buffer ). Please note that depending on the type of buffer youre using the size
of your buffer never changes(only grow types change). So if you are not using a grow type buffer,
adding data to your buffer will never change the initial size of the buffer.
Getting the size of the buffer is as simple as this(example):
var buffersize = buffer_get_size( Buffer );
On other occasions you might want to get the size in bytes of a specific data type. You can do this
using: buffer_sizeof( type ). This function will return the size of any data type in bytes.
You can use it like so(example):
var sizeof_type = buffer_sizeof( buffer_u8 );
Now, another important thing to do with buffers is to delete them. This is because buffers use
dynamic memory. Meaning that the buffer will remain in memory when you're done using it if
you don't delete it! All dynamic memory must be deleted or it'll stack up and cause your game to
run out of memory and collapse in on itself. To delete a buffer you can use: "buffer_delete( buffer
)".
Deleting a buffer is as simple as:
buffer_delete( Buffer );
This concludes basic use of buffers for creating, reading, writing and other simple tasks using
buffers.
--------------------------------------------------Data Structures---------------------------------------------If you didn't know, data structures are crucial to handling data with networking. For example in
this tutorial, we create a ds_list or (d)ata (s)tructure list to store our client's socket IDs when they
connect to the server. So let us walk through data structures.
Just like buffers, data structures are another form of data storage. There are even multiple types of
data structures: ds_map, ds_list, ds_grid, ds_queue, ds_priority and ds_stack. That is a lot to
cover, however we will only be covering ds_lists and ds_maps as these are pretty easy to
understand.
First though, let me point out that data structures---like buffers---use dynamic memory.
To start off, lets learn about ds_list. A ds_list lets us store a basic list of data where each piece of
data is given a position in the list to which we can reference back to the data later on. So let us
create a ds_list and add data to it, reference the data back and finally delete it:

You can create a ds_list like so:


MyList = ds_list_create();
The above code will simply create your ds_list and return it's ID into a variable which we can use
to call the ds_list later on to add or reference data from, easy right? So let us go ahead and add
some data to our new ds_list. We can do this via ds_list_add( list , value ) like so:
ds_list_add( MyList , 16 );
Simply enough? This will add value 16 to our ds_list, MyList. Now when you add data to a
ds_list, the ds_list puts the data at the end of the list. So if you have no data in your list, then the
value added(in this case 16) will be added to the first position in the ds_list. The first position in a
ds_list is 0. All data added after will be added chronologically at positions: 1, 2, 3, 4, etc. So now
that our data is added, let us find our data in our ds_list, MyList. Since no data was added before
our value of 16 then our value is added at position 0. So we'll need to get(or reference) the value
back from position 0 using "ds_list_find_value( list , position )" like this:
var value = ds_list_find_value( MyList , 0 );
As you'll notice the code above find(or refences) our value at position 0 that we added into the
ds_list, MyList, and returns it to the variable "value". Sometimes you may not want to only find
and add data to a ds_list, but even "insert" data into a ds_list. Insert meaning, take your value and
add it to a specific position in a ds_list. However, doing so takes all data at and after the position
the value is being inserted to and moves the data up one position. Example: If you have values: 2,
4, 6 and 8 at positions: 0, 1, 2 and 3, inserting value 16 at position 2, would move values 6 and 8
from positions 2 and 3, to positions 3 and 4. Easily enough we can insert data into a ds_list using
"ds_list_insert( list , position , value )" like so:
ds_list_insert( MyList , 0 , 8 );
Sometimes you might even want to delete a value from a position in a ds_list. For example, we
might want to delete our value 8 from our position of 0 in the ds_list using
"ds_list_delete( list , position ) " like this:
ds_list_delete( MyList , 0 );
Finally when we're done using our ds_list as in, we no longer need it, we need to delete the ds_list
from the dynamic memory. This can be done using "ds_list_destroy( list )" like so:
ds_list_destroy( MyList );
There is a lot more information on ds_lists, such as more functions for them in the Game Maker
help file, so make sure to check that out. This concludes basic use and information on ds_lists.

Moving on, we next have ds_maps. Unlike ds_lists, ds_maps do not use positions to find data, but
rather, "keys" to find data. A "key" is a user defined reference to a piece of data. FOr example if

we had a piece of data representing our server's IP address, we could store the IP address under a
key in a ds_map then later get the IP address back again using the key.
Let us first create our ds_map using "ds_map_create()" like so:
MyMap = ds_map_create();
Like creating a list, the above function creates our ds_map and returns it's ID into the variable
MyMap for which we can use later to reference back to the ds_map. Now you might want to add
a value to a ds_map under a specific key, you can do this using "ds_map_add( map , key ,
value )" like below(using the previous example explained):
ds_map_add( MyMap , "Server IP" , "127.0.0.1" );
Easily enough, the above code adds our IP address "127.0.0.1" to a key our key i nthe ds_map
"Server IP". Now that we have added that to our ds_map, MyMap, lets get the IP address back
using the key with function "ds_map_find_value( map , key )" like so:
var ipaddress = ds_map_find_value( MyMap , "Server IP" );
ds_maps do not deal wth positions, so their is no finding values by positions or inserting data at
specific positions like ds_lists. Thus we can simply find and add values but not insert. Now we
might want to delete our key and it's value from our ds_map, MyMap. Which can do using
"ds_map_delete( map , key )" like below:
ds_map_delete( MyMap , "Server IP" );
Liek nay data structure, ds_maps use dynamic memory thus we need to delete our ds_map when
we no longer have need for it, this can be done using "ds_map_destroy( map )" like this:
ds_map_destroy( MyMap );
There is a lot more information on ds_maps, such as more functions for them in the Game Maker
help file, so make sure to check that out. This concludes basic use and information on ds_maps.
-------------------------------------------------End Of Tutorial-----------------------------------------------

You might also like