Events Overview¶
Events encapsulate communication between the game process and all
of its spectators. They can be used to funnel spectator inputs,
analyze and respond to chat events, inspect logs, etc.
They are composed simply of a key:value
pair.
The basic scalability is designed around a MapReduce algorithm which filters the events coming from multiple sources (web spectators, game client, live chat, logs, etc.) and combines them into a limited number of outcomes that has a drastically reduced impact in both frequency and bandwidth.
Fundamentally, events go through the following flow:
- Event Generation (typically issued by the spectator in JavaScript).
- Event Filtering (done using MapReduce inside the Genvid Cluster).
- Event Consumption (by the game, the web client, or some other component inside the Genvid Cluster itself).
Event Generation¶
Let’s take the case of viewer input. When the viewers interact with
the stream inside their browser, developers can trigger events in the
system. Those events are sent using a key:value
pair.
Here is how the JavaScript API would work:
genvidClient.sendEvent(["player", "like"], "player1");
In this example, a spectator would report liking "player1"
,
which corresponds to the value, whereas the key would be composed of a
list of 2 terms, namely "player"
followed by
"like"
.
Event Filtering¶
Because every spectator can send events, we could end up with an arbitrary number of events that the game process is incapable of handling in a timely manner.
To solve this problem, all events sent go through a MapReduce set of filters meant to transform a potentially huge number of events into a much more reasonable count.
Those filters are specified using JSON files and deployed to the cluster.
Developers must pay special care as to design both the Map and Reduce portions of the filtering step.
Map example¶
In the events system, a map takes key:value
pairs as input and when
they meet certain conditions, transforms that input into another
key:value
pair. This step is referred to as the Mapping step.
A map is described in JSON and describes the transformation.
{
"id": "playerlike",
"source": "viewers",
"where": {
"key": [ "player", "like" ],
"name": "<like>",
"type": "string"
},
"key": ["like", "<like>"],
"value": 1
}
In this example, when a key matching ["player", "like"]
enters the
system, a new key:value
is produced. The new key becomes ["like",
"$value"]
, where "$value"
corresponds to the string value assigned
to that key (e.g., "player1"
in the previous example).
The value of the result will simply be assigned to 1.
For example, if we had the following events coming for multiple viewers:
{ "key": ["player", "like"], "value": "player1" }
{ "key": ["player", "like"], "value": "player1" }
{ "key": ["player", "like"], "value": "player2" }
{ "key": ["player", "like"], "value": "player1" }
the map named playerlike
would transform these into the following:
{ "key": ["like", "player1"], "value": 1 }
{ "key": ["like", "player1"], "value": 1 }
{ "key": ["like", "player2"], "value": 1 }
{ "key": ["like", "player1"], "value": 1 }
Once the data is transformed, we can then apply the Reduce operation.
Reduce example¶
The Reduce operation takes key:value
pairs as input and
transforms them into another set of key:value
pairs, but merged
together in order to have fewer of them.
The Genvid reduce system is also writen in JSON. Let’s see an example
{
"id": "playerlike",
"where": {
"key": ["like", "<playerName>"]
},
"key": ["like", "<playerName>"],
"value": ["$count"],
"period": 1000
}
This reduction step matches any key starting with "like"
then followed by anything; whatever second parameter is matched will
be referrable as the token playerName
in later steps.
Any matching key:value
will then be merged, and the resulting value
will be the total count, i.e., the number of times the original key
was encountered. The resulting key, being assigned to
"$key"
, will simply be carried from the original data.
So assuming we receive the following results from the map operation:
[
{ "key": ["like", "player1"], "value": 1 },
{ "key": ["like", "player1"], "value": 1 },
{ "key": ["like", "player2"], "value": 1 },
{ "key": ["like", "player1"], "value": 1 }
]
the reduce operation would merge them as follows:
[
{"key": ["like", "player1"], "value": "3"},
{"key": ["like", "player2"], "value": "1"}
]
Event Consumption¶
Events would be meaningless if no one ever received them.
In order to receive the final results of the Filtering step, a user must subscribe to the output of a Reduce rule.
This is done by specifying both the id
of a reduction output.
{
"id": "playerlike"
}
This subscription tells the system that the reduction named
playerlike
would send its result back to the subscriber every
second.
Note that the system is memory-less, meaning that once the data is sent, all internal data is wiped and reset.