Jump to content
Sign in to follow this  
firefly2442

Locality of Variables Between Client/Server

Recommended Posts

I have a variable on the client which needs to be passed to a function on the server and then only the server should run the function. Then I need it to return some results back to just that client. I'm trying to read up on locality but I can't find anything in terms of examples for passing data between the client and server. Does anyone have any code snippets that would help?

Thanks in advance.

Share this post


Link to post
Share on other sites

This is achievable through public variables (PVs) and public variable event handlers (PVEHs).

There is also the MP Framework which should be handle this as well, but I usually prefer doing it manually.

I am working on some examples of the PV an PVEH method. Example below...

EDIT:

OK, below is an example. This should work but it is untested. it is a little complicated but I have tried to explain as best as possible. We may be able to simplify it depending on what you are actually trying to do.

Here is the concept. You have two PVEHs, one on the server that waits for the client to broadcast a value, takes action on that value, and broadcasts a new value to ALL clients. The second PVEH is only on clients and waits for the server to broadcast the return value AND a reference to the original caller. If the caller does not match, then no action is taken. If it does, it does something with the value. See important note at the bottom.

// init.sqf

// SERVER //

// If the PV is nil then assign it an initial value.  In this case an array containing an object and an integer.
if (isNil "TAG_serverEvent") then
{
     TAG_serverEvent = [objNull, 0]; 
}; 
// else public variable has already been set due to a public variable broadcast.

// If this is a server then add a PVEH that will execute a script whenver the PV changes and pass to the script the new value.
if (isServer) then
{
   /*
       _this select 0: variable name
       _this select 1: new variable value
   */
   "TAG_serverEvent" addPublicVariableEventHandler {[(_this select 1)] execVM "server.sqf";};
};


// CLIENT //

// If the PV is nil then assign it an initial value.
if (isNil "TAG_clientEvent") then
{
     TAG_clientEvent = [objNull, 0];
}; 
// else public variable has already been set due to a public variable broadcast.


// If this is NOT a server then add a PVEH that will execute a script whenver the PV changes and pass to the script the new value.
if (!isServer) then
{
   /*
       _this select 0: variable name
       _this select 1: new variable value
   */
   "TAG_clientEvent" addPublicVariableEventHandler {[(_this select 1)] execVM "client.sqf";};
};

// somethingOnClient.sqf

// Something happens on the client and we want to broadcast a new value of TAG_serverEvent.
// In this case pass the player's object and an integer.
// Once the PV is brodcast the PVEH added to the server should fire.
TAG_serverEvent = [player, 1]; 
publicVariable "TAG_serverEvent";

[color="DarkGreen"]// If this is a MP host or SP then directly execute server.sqf and pass to it the value since the PVEH will not fire on the macine that broadcasts the PV.
if ((isServer) && (!isDedicated)) then
{
   _nul = [TAG_serverEvent] execVM "server.sqf";};
};[/color]

// server.sqf

// This script is executed by the PVEH added to the server fires when somethingOnClient.sqf broadcasts the PV or [color="DarkGreen"]directly by somethingOnClient.sqf
if an MP Host or SP[/color].

// Set Scope.
private ["_newValue", "_caller", "_number", "_total"];

// This is the new value to the script by the PVEH.
_newValue = _this;
_caller = _newValue select 0;
_number = _newValue select 1;

// Peform a calcuation using the number passed from the client.
_total = _number + 1;

// Broadcast a PV with the name of the original caller and the total of the calculation.
TAG_clientEvent = [_caller, _total];
publicVariable "TAG_clientEvent";

// client.sqf

// This script is executed by the PVEH added to the client fires when server.sqf broadcasts the PV.

// Set Scope.
private ["_newValue", "_caller", "_number", "_total"];

// This is the new value to the script by the PVEH.
_newValue = _this;
_caller = _newValue select 0;
_number = _newValue select 1;

if (_caller == player) then
{
   // Peform a calcuation using the number passed from the server.
   _total = _number;
};
// Else the player is not the caller and skip the calculation

Note: The above will probably not work for the host on an MP hosted game because PVEHs do not fire on machines that broadcast them. If you need MP Host compaibiity let me know and I will show you how we modify it to work there as well.

Edit: New new text in green somethingOnClient.sqf for MP Host and SP compatibility.

Edited by Loyalguard

Share this post


Link to post
Share on other sites

CBA makes it easier.

http://dev-heaven.net/projects/cca/wiki/Custom_Events_System

Simple example of sending a player's position to the server and then running a script with that as a parameter only on server:

http://dev-heaven.net/projects/tasm/repository/revisions/master/show/addons/sys_gear_mission

Edited by Robalo_AS

Share this post


Link to post
Share on other sites

Wow, this is fantastic, thank you!

So MP host compatibility would be really great, especially for testing purposes.

One question: Client.sqf then will only be run on the clients that are setup using the public variable event handler? In what we're trying to build, each player will be setup for this system so I guess they will all be incurring the bandwidth costs of receiving the data but skipping the CPU costs of using it?

Share this post


Link to post
Share on other sites

Also recommending the CBA Events, for one, they use only a single global variable, saving a lot of useless states to be synced at each JIP ;) As well as a useful API for triggering the events locally or globally, and even whereObjectIsLocal.

Also have a look at http://community.bistudio.com/wiki/6thSense.eu:EG for some notes on MP scripting in general incl PV etc.

There is also the MP Framework which should be handle this as well, but I usually prefer doing it manually.
Shame on you for even mentioning the BI MP Framework :P Edited by Sickboy

Share this post


Link to post
Share on other sites
Shame on you for even mentioning the BI MP Framework :P

Is this not a good idea to use? I use it quite extensively for things like sideChat and sideRadio as I find it infinitely easier than the pV method.

Share this post


Link to post
Share on other sites
Is this not a good idea to use? I use it quite extensively for things like sideChat and sideRadio as I find it infinitely easier than the pV method.
The framework is inefficient (sends full script file names over the network), can only execute code async (execVM is used) IIRC, at least for the initalization it is.

It is therefore also susceptable to script load (/lag), and the system might be initialized late depending on the script load.

Sending strings over the network is bad design as well, unless you are creating a client-side or server-side only project.

CBA Events example:

Init:

// Add EventHandler on all machines (init.sqf runs everywhere)
["MyRadioEvent", {
  private ["_player", "_otherUnit"];
  _player = _this select 0; _otherUnit = _this select 1;
  _player sideChat format["Hey %1, it's a nice sunny weather out there, isn't it?", name _otherUnit];
}] call CBA_fnc_addEventHandler;

Somewhere you want to raise the event:

// Raise MyRadioEvent, only sends parameters (object references) over the network
["MyRadioEvent", [player, someOtherUnit]] call CBA_fnc_globalEvent;

As you can see:

  • We register eventhandlers on every machine through init.sqf, this contains the code and the string (you could add localize if you move the string to a stringtable of course). This requires no network traffic.
  • Once we need to raise the event, we only send object references over the network, of the objects participating in this event.

There is also localEvent, remoteEvent, whereIsLocalEvent etc; several variants to fullfill all your needs.

You can also limit the machines where you run the addEventHandler code for more fine grained control if a machine will handle an event when raised.

The advantages of using our API compared to the raw engine functions (PV + addPVEH etc), is that our system only uses 1 global variable that will be used for communication, saving the (useless) synchronization of initial state of many variables for JIP players.

As well as comfortable API functions that should ease usage.

One disadvantage is that it does not have a persistency layer available out of the box, for that, and only that, I would still use setVehicleInit.

I think the MP Framework handles persistency (optional), by storing commands in an array, and making this array public, or sending the commands one by one on JIP. We could implement a similair system to CBA events.

Edited by Sickboy

Share this post


Link to post
Share on other sites

I also recommend the CBA event system.

If you do not want certain players to receive the events you just don't add the event handler on their machine. You don't have to use it only to get messages across the net. You can also use the events locally only if you know what you are doing by using CBA_fnc_localEvent.

Another nice pattern I use is, if you want say create a task for all current players and JIP. You want to the server to notify players of new tasks. You have some function that creates the task. You add an event handler for all clients that call this function to create the task whenever the event is called. On the server you also have a event handler for this event but this stores the required data somewhere. When new clients connected they check that data stored and call the very same function to create the task so they see the same as everybody else.

If you later need to delete the task you have a function to do that too. Then on all clients their event handlers call the delete function. On the server it's event handler removes the task data from the JIP data that clients read on connect.

Edited by Muzzleflash

Share this post


Link to post
Share on other sites
Wow, this is fantastic, thank you!

So MP host compatibility would be really great, especially for testing purposes.

I have added some new code in green that adds MP host and SP compatibility.

One question: Client.sqf then will only be run on the clients that are setup using the public variable event handler? In what we're trying to build, each player will be setup for this system so I guess they will all be incurring the bandwidth costs of receiving the data but skipping the CPU costs of using it?

Yes, the way PVs work is that when you broadcast them -- they go to all machines, servers and clients, indiscriminately. I am unware of a way of only sending info to only one machine (probably not possible with engine net code). When this happens, the PV's value is updated on all machines (to include those that JIP later) until it is overwritten or updated again. But, if there is no PVEH, then no action will be taken (even though the new value will exist). If a PVEH has been set up on a machine, the PVEH will fire and action will occur. In my examples above, unless you limit it, the PVEH will be on all clients, but as you have stated, once the script performs the caller check, if it is not the correct machine it will abort the remainder of the script.

CBA makes it easier.

Agreed! If you have CBA installed and it will be installed on all machines then I agree using CBA is definitely easier than my convoluted code..it depends on how you have things configured for your purposes.

Edited by Loyalguard

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×