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.