Jump to content

net_variable types and sending data from server/host to clients


Recommended Posts

## Client / Server Simulation Relationship

Mods should use these always to be clear about which side you want code to run on;
by default all Lua code runs on BOTH client and server side,
and this can be very wasteful!

### Definitions:

- client host: DST > Play > Host Game > Generate/Resume World
- remote client: DST > Play > Browse Games > Join
- dedicated: Steam > Tools > Dont Starve Together Dedicated Server

|                      | client host | remote client | dedicated |
------------------------------------------------------------------
| TheWorld.ismastersim |    true     |     false     |   true    |
| TheNet.GetIsServer() |    true     |     false     |   true    |
| TheNet.IsDedicated() |    false    |     false     |   true    |
| TheNet.GetIsClient() |    false    |     true      |   false   |

`TheWorld.ismastersim` == `TheNet:GetIsServer()` // this is running on a computer hosting the game
`not TheWorld.ismastersim` == `TheNet:GetIsClient()` // this is running on a computer joining a game
`TheNet:IsDedicated()` // this is a headless dedicated server

NOTICE client host means client and server share a single simulator;
       evaluating one instance of the logic for both client and server.


## Netvars (Network Variables)

A collection of classes representing platform-agnostic data types;
streamed change events synchronize their values over a networked machine cluster.

The intended use case for netvars is to be exclusively `:set()` by the server,
while `:value()` is read by the client(s). Although `:set_local()` can be used by
any side, its most useful on the client-side for network lag interpolation 
between `:set()` values and subsequent dirty events.

The interface for dirty event listeners is `f(e:Entity):Void`.

Netvars must be defined on both sending and receiving sides before 
changing values will cause a sync or dirty event.

fig. 1: `Entity:ListenForEvent()` will trigger
| if listening from... | and `:set()` on...                       |
|                      | client host | remote client | dedicated  |
|----------------------|-------------|---------------|------------|
|          client host |    true     |     false     |     -      |
|        remote client |    true     |     false     |   true     |
|            dedicated |      -      |     false     |   true     |

fig. 2: `NetVar:set()` will always change local `NetVar:value()`,
but it will only sync
| `:value()` of...     | if `:set()` on...                        |
|                      | client host | remote client | dedicated  |
|----------------------|-------------|---------------|------------|
|          client host |    true     |    false      |     -      |
|        remote client |    true     |    false      |   true     |
|            dedicated |      -      |    false      |   true     |

Its not an error to `:set()`, or `:set_local()` from the client, but realize it
only changes it for that one local client instance. If you do it mistakenly,
you will find the `:value()` is not the same on the server or other clients.

If you bind a new netvar to an Entity GUID that only exists on the client,
or an Entity that does not have an `:AddNetwork()` component attached to it,
it will NOT sync or trigger, neither locally or remotely.

Binding a netvar to an Entity GUID that does not exist on the client side
will cause a hard crash on the client.

Network protocol is an un-encrypted implementation of RakNet mainly udp/10999
http://raknet.com/raknet/manual/

```
interface NetVar<T> {
    /**
     * Register event handler for network variable of a given name.
      *
     * @param guid - Entity GUID.
     * @param name - Network variable name. Must be unique per entity.
     * @param dirtyEventName - Can be bound by `Entity:ListenForEvent()`. See set() for details.
     */
    public function constructor(guid:GUID, name:String, ?dirtyEventName:String):Void;

    /**
     * Unidirectional setter;
     * - Sets new value locally
     * - Triggers dirty event listeners (see fig. 1 above)
     * - Syncs new value from server to client(s) (see fig. 2 above)
     *
     * Will no-op unless one of the following is true:
     *
     * - given value is different than last value, or;
     * - `set_local()` used one or more times since last `set()`.
     */
    public function set(value:T):Void;

    /**
     * Local-only setter;
     * Does NOT notify the cluster or trigger dirty event listeners.
     *
     * Recommended for frame-rate-dependant interpolation.
     * (e.g. client makes guesses locally until corrected by the server)
     */
    public function set_local(value:T):Void;

    /**
     * Local-only getter;
     * Return last value received by network sync, :set(), or :set_local().
     */
    public function value():T;
}


/**
 * 1-bit boolean
 *
 * Default: false
 *
 * e.g., set_local(true); set(true); can be used on net_bool
 *   to transmit event notifications without having to
 *   toggle true on/off between each event.
 */
interface net_bool extends NetVar<Bool> {};

/**
 * 8-bit unsigned integer   [0..255]
 */
interface net_byte extends NetVar<Int> {};

/**
 * array of 8-bit unsigned integers (max size = 31)
 *
 * Arrays are expensive; avoid if you will dirty them often.
 */
interface net_bytearray extends NetVar<Bytes> {};

/**
 * unsigned 64-bit integer containing entity network id
 * represents the entity instance
 */
interface net_entity extends NetVar<Entity> {};

/**
 * 32-bit float
 */
interface net_float extends NetVar<Float> {};

/**
 * 32-bit unsigned integer
 *
 * If value is a number, it is forwarded as the hash.
 * If value is a string, it is converted to a hash before sending.
 *
 * REMINDER: The hash algorithm is deterministic, so expect
 *   clients to agree on output value regardless of platform.
 */
interface net_hash extends NetVar<Hash> {};

/**
 * 32-bit signed integer    [-2147483647..2147483647]
 */
interface net_int extends NetVar<Int> {};

/**
 * 16-bit signed integer    [-32767..32767]
 */
interface net_shortint extends NetVar<Int> {};

/**
 * Maps to an unsigned 32-bit integer.
 */
interface net_uint extends NetVar<Int> {};

/**
 * 16-bit unsigned integer  [0..65535]
 */
interface net_ushortint extends NetVar<Int> {};

/**
 * 3-bit unsigned integer   [0..7]
 */
interface net_tinybyte extends NetVar<Int> {};

/**
 * 6-bit unsigned integer   [0..63]
 */
interface net_smallbyte extends NetVar<Int> {};

/**
 * array of 6-bit unsigned integers (max size = 31)
 *
 * Arrays are expensive; avoid if you will dirty them often.
 */
interface net_smallbytearray extends NetVar<Bytes> {};

/**
 * variable length string
 */
interface net_string extends NetVar<String> {};

/**
 * A convenience wrapper over net_bool.
 * does the set_local(true) set(true) trick for you.
 *
 * Use for one-shot triggers.
 */
interface net_event extends NetVar<String> {};
    /**
     * Remote trigger.
     *
     * Like set() but no value is required.
     */
    public function push(): Void;
}
```

 

Edited by mikesmullin
typo
  • Like 5
  • Thanks 3
Link to comment
Share on other sites

On 2/1/2015 at 22:33, PeterA said:

 

 

How can I place a variable sum, so that it is saved and starts 1 and adds itself to incremartar.

 example: sum = 1

in use sum = sum * 0.9

but that the variable is not reset when closing the game. i need it to value save  with the game

i no speak english

Link to comment
Share on other sites

On 1/4/2015 at 4:24 AM, V2C said:

2. The GUID parameter is just used locally to map back to C-side entity.  The value of the GUID itself is not meaningful in this case, and it was simply chosen to match most of the legacy lua bindings.

Hi, V2C, I am recently learning how to use netvars, great to find this post, and thanks for your explaining.

I have some more questions though:

1. "The GUID parameter of netvars is not meaningful", does that mean it does not necessarily need to be the actually GUID of an existing inst, it can be any value or even nil ?

2. Where should I declare these netvars ? The Hunt uses them in a Class, can I declare it right in modmain.lua or a PostInit function?

Edited by asingingbird
  • Like 2
Link to comment
Share on other sites

I have a very weird problem with netvars and forest_world.lua, I think it is a game bug, a regression on top of it. Can I have some educated opinion? (Warning: a bit technical).

Basically, I made a mod to add earthquakes on surface. For that I add the existing game component "quaker" to TheWorld. In the past, that's all that was needed and the earthquakes worked perfectly well, but recently, I've noticed that there is no longer the earthquake sound anymore. The sound is managed with Netvars, when the earthquake starts on server, server changes a netvar, and clients received an event "OnQuakeSoundIntensityDirty' that is supposed to trigger sound on their end. The issue is that for some very weird reason, the "dirty" event is not triggered anymore when the variable is :Set() by the server. I am 100% sure that this worked a long time ago. Unfortunately I haven't played for more than a year so I can't tell when exactly the change happened.

I reviewed the code of quaker.lua and to me it looks correct, see below. I've debugged it extensively, renamed everything, and checked that the right code paths are taken, and I have no other explanation than game bug, the variable is properly set by the server and yet it does not trigger the "dirty" event.

-- Note: Code taken from quaker.lua with minor changes.

-- this is how the net_var is created, at component init:
local _quakesoundintensity = net_int(inst.GUID, "quaker._quakesoundintensity", "quakesoundintensitydirty")
inst:ListenForEvent("quakesoundintensitydirty", OnQuakeSoundIntensityDirty)

...

-- and this is the code called at runtime:
local StartQuake = function(inst, data, overridetime)

	if _ismastersim then
	
		_quakesoundintensity:set_local(1)
		_quakesoundintensity:set(2)
	
		-- I would expect the "Dirty" function to be called here or shortly after the function call
		-- but it's never called, even on a client hosted world. As a result the earthquake makes no sound.
	
		print(_quakesoundintensity:value())     -- prints 2
	
		-- If I manually push the event like this, then the sound works:
		-- TheWorld:PushEvent("quakesoundintensitydirty")
		-- (but it won't work on dedicated server)
		
	end

...

end

 

The issue does not appear in vanilla to my knowledge, but it ruins a great mod and is a big annoyance to debug. Any help appreciated.

 

Link to comment
Share on other sites

Might be worth noting (and I'm unsure if this might be the cause for the issue in the comment above or not, as I'm not sure how it's fully set up), but it seems that master shards and secondary shards have the same kind of authority as server and client, in that master shards can set and sync things to secondary shards, but not the other way around (however secondary shards in the master sim will still sync things to clients in them, if applicable).

I had some stuff set up for a mod I was working on where I had to account for this, and have the cave shard send an rpc to the master shard, which would then set the variables and sync it to everyone, including the cave shard that sent the rpc. And then Klei did something like this (with a few differences) for daywalker and daywalker2.

I thought it was worth mentioning, and might be worth adding this info here and/or the netvars.lua file, as it only explains interactions between server and client, but not between master and secondary shards.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...