Introduction
The decision to build the Twitch plug-in and game around a local server largely informs their design. The following design was written during tracer prototyping of the Twitch plug-in and was refined as I moved through that process. I anticipate that things will still end up deviating from this design to some degree during production as unforeseen problems arise. I am not considering these deviations failures during this project as the intent is to develop this as an agile project, not a waterfall one.
On the note of this being an agile project (see the development methodology document), this design is not meant to be fully comprehensive, listing every function, object, etc. Instead, it’s focused on defining the major components of the project, their purpose, and how they interact with each other.
Note that the section of this document dedicated to the game will be brief as this is a stretch-goal. The design of the plug-in has been prioritized first.
Twitch Plug-in
As mentioned in the analysis document, the plug-in will utilize a local NodeJS server. The plug-in will be designed around a client-server model with distinct separation between the client and server. The reason for this separation is twofold:
First, it’s to facilitate encapsulation of functionality to make extending this plug-in easier in the future, to make the code more approachable, and to promote reusability.
Second, it’s due to the technical constraints of a browser. Outside of cookies, browsers do not write data to a local storage device which makes guaranteeing persistent data more challenging. We also require the ability to refresh browser pages without losing data integrity, and to enable different pages to communicate between one another. This is all handled by our authoritative server.
The Twitch plug-in is separated into five distinct modules and some helpers. The modules are the server, the configuration page, the timer page, the donation stats page, and the donation notification page.
Server
The server is meant to act as an authoritative mechanism for handling storage and consistency of data. It acts as the source of truth for all client-side modules. This allows client-side modules to be destroyed and instantiated without affecting the current state.
To facilitate this data consistency, the server saves and retrieves data to and from the disk. By writing to disk, the user can be sure that data will last between sessions, even in the event of an unexpected server shutdown. In order to limit the amount of disk reads used to retrieve data, all known state is pulled from disk into memory upon server initialization.
Another important function of the server is to handle incoming data from active clients and push out relevant data to clients that want to see it. This is handled through a persistent web socket connection between the server and various clients.
Functionality
The server is created as a websocket/Express server hybrid. Express manages serving our ‘static’ pages that form the pages the streamer can then access at a local address. This includes images, client-side JS code, html docs, and CSS docs.
The websocket server manages new and existing client socket connections. Clients establish a connection with the server on a specific local port. The server also manages client ‘heartbeats’ which checks if clients are still active using ping/pong messages. Typically, the client will notify the server that the connection is being closed, but in the event this doesn’t happen (a common occurrence with browsers), the server will automatically close the connection if a heartbeat fails. At the time of this writing, heartbeats occur once every 30 seconds.
The server acts as a message handler and sender. Clients send specific messages to the server, and the server can expect certain data based on the message type. These messages are a custom struct I have defined and fall under the following types:
- Timer config updates: changes the remaining time on the timer
- Donation config updates: changes to donation timer values
- Timer start/stop requests
- Donation events: events that are triggered when a donation occurs
- Debug donation events: events that trigger a false donation so that the streamer and I can test functionality without being live
- Subscription events: requests from clients to be added to the subscription lists for specific events, allowing them to be notified when these events are fired
It’s also the server’s job to manage event subscriber lists. As mentioned, clients can send a subscribe request to the server along with event types. When this happens, the server records the client’s unique socket UUID to a dynamic list for the requested event type and sends the current state relevant to the event back to the client. Then, when the server receives messages that need to be broadcast, or decides to broadcast one itself, it will notify all clients contained within the relevant subscriber list. The subscription system’s purpose is to abstract the concept of specific client ‘types’ so that the server doesn’t need to care what a specific client’s purpose is, only that it wants to know about specific events. Clients can then manipulate the data (or ignore it) as they wish.
As I mentioned earlier, the server is also responsible for file IO. Upon initialization, the server will check all known data files to see if information exists within them. It accesses each file with a unique file key string defined in a common helper. If a file exists, it will pull the current data into memory in order to limit the number of disk reads that occur. On the other hand, data updates are always written to both memory and disk. While not ideal, this is done to cover the event that the server fails or is accidentally closed by the streamer. Unfortunately, server destruction events aren’t extremely reliable in NodeJS servers, so we can’t depend on them to ensure data is written in the event of a server closure. Luckily, at most we are writing a few small structs of data each second, so it’s not extremely strenuous.
Lastly, the server handles the APIs related to Twitch and Extra-Life. For Twitch, the server handles authentication using a registered application ID (already generated). Twitch also uses a subscription socket-based system to subscribe to events. The server subscribes to events relevant to donation tracking (subscriptions, cheers (bits), and follows). Extra-Life on the other hand doesn’t have an event-based subscription system, so instead the server will perform manual calls every 5 seconds to the API to check if the state has changed since our last check. First, it looks at the total donation amount and compares it to the last known. If the amount has changed, the donor list is pulled and recent donations are broadcast to subscribers.
Organization
The majority of core server functionality is implemented in Server/configServer.js. This file contains the aforementioned server setup, socket connections, subscription management, and event management functionality.
The server also relies on file IO helpers contained within Server/serverFileHelpers.js, and a common interface helper containing message type definitions and data structure definitions located in Interface/configInterface.js.
All aspects of the project are organized into modules to allow for more granular import and exports. The server is no exception to this rule, living within its own module and importing helper functionality as needed.
External Packages
The server relies on several open source NodeJS compatible packages:
- Express: for serving client-side files
- WS for websocket server and connections
- UUIDv4 to assign unique ids to client sockets to enable subscriber registration and management
- FS for file system management
Interface
The application contains a common interface layer that both the client and server use.
Functionality
It provides functionality and definitions that both the server and client require. This includes message and configuration data structure definitions, and a base interface class definition. The base interface class (IConfig) can be inherited to provide child objects with the ability to connect to the server, subscribe to events, and send messages.
All socket connections are managed through built-in websocket functionality found in most modern browsers (Firefox, Chrome, Edge, and the built-in browser of the streamer’s stream tool).
Organization
The common interface is all contained within a single file: Interface/configInterface.js. This file is set up as a module within the build so that it can export its data definitions to be imported by other modules.
Client
The client-side portion of the Twitch plug-in is separated into four parts: the timer page, the configuration page, the donation stats page, and the donation notifications page. Each of these components are represented by their own CSS-formatted HTML pages whose functionality are driven by JS modules instantiated with each page instance.
Functionality
Each page serves a specific purpose and can have a mix of taking input from the user and displaying output. Specifically, the config page takes inputs from the user to affect the timer and donation configurations. The timer page is purely visual, displaying the current timer state. The donation stats page also just displays data, showing the most recent donation as well as the top 3 donations. Lastly, the donation notification page is an event-driven page that only displays content when certain donation events trigger. Otherwise, it is completely transparent. These visual pages are all intended to be used as browser sources within the streamer’s stream tool.
The JS modules are subclasses of the IConfig interface class and, using the helper functions provided by the IConfig class, handle the socket connection and sending of messages for their page. They also use these helpers to handle notifying the server of their desire to be subscribed to certain event types and handle incoming messages that are sent by the server for those events. There is no obligation for the client to handle an incoming message from the server.
Organization
All of the client-side files are stored within the project’s Frontend folder and are further divided based on their file types (css, html, img, js). As mentioned, each client page has its own JavaScript, HTML, and CSS pages associated with it. Images can be shared between pages if necessary.
Game
As stated at the beginning of this design writeup, the following game design will be extremely brief given that this is a stretch-goal. This is being done to ensure time is being spent on the primary goal of this project. The project’s client agrees that time is better spent prototyping and implementing the plug-in rather than designing the game. If the game is pursued later in this project, additional design can be fleshed out if necessary.
Mechanics
The game will have very simple mechanics. The idea is that when a hype-train event occurs in chat, the game will fade into its associated browser source, music will play, and characters representing the users in chat will appear beneath a static image of a ‘boss’. The players will use chat commands to attack the boss in different ways, trying to defeat it before the time runs out. For more specifics on game mechanics, see the requirements document.
Architecture
The game will be set up in a very similar way to the Twitch plug-in in that it will use a server-authoritative system, with the browser front-end performing simulation and display depending on what the server has provided it for current state. In fact, the game server will likely just be an additional module attached to the plug-in server. The idea behind this is making it so the user only needs to launch one server and that we can piggyback on existing server functionality with web sockets. Technically, for the game on its own, a server isn’t required. The game library, Phaser, lives completely within the browser. That said, it will be easier to use existing server functionality including connections with twitch, processing donations, and managing users than it would be recreating this functionality in the browser just for the game.
Server
The server will perform several jobs for the game including viewer interaction and boss simulation.
First, it will establish an additional connection with the Twitch event server to monitor chat actions. It will parse the chat and look for any commands that start with ‘!’ (the standard Twitch command symbol). If it finds commands relevant to the game, it will record the user’s name and action type. The server will then process this information and deliver it to the client in order to display on-screen text showing the action.
Second, the server will manage the boss’s state change between different ‘modes’. These modes will determine what actions the viewers should take in chat. Each state will have a percentage chance associated with it. Using these chances, the game will pick the next state at random with the only caveat that it must be a different state than the current one. This will help prevent situations where the boss stays in one state for a very long time due to the nature of randomness. The server will also manage the boss’s health, adjusting it for chat actions or donations that happen during gameplay. Management of the boss’s attacks and viewers that are killed by them will also be done server-side.
Lastly, it will push the current state to the front-end browser source so that it can update the current display shown in stream. This will include the boss’s health, the users that are still alive, the time remaining, and any events such as user commands and donations that are happening.
Client
The client will implement the ‘actual’ game excluding the state data handled on the server. This involves displaying the active viewers based on the list sent by the server, showing the boss image, updating the health bar display, and updating the timer display.
Viewers will be represented at the base of the screen by a random assortment of 2d avatars. Each avatar will be associated with a single viewer, displaying their name. The avatars will move around the bottom of the screen at random intervals to give the game a greater sense of movement. When a viewer enters a command, their avatar will jump and a symbol representing their action will appear above their head. As players die, they will play a quick death animation and disappear from the view.
The timer will be a simple text timer at the top-right corner of the screen and the health bar will be a simple filled rectangle at the top left.
Phaser
The client will use Phaser to manage the front-end side of gameplay. This includes animation, drawing 2d images to screen, and any physics simulation if required. Phaser is an HTML5 based library, meaning that it lives completely within the browser and doesn’t require any server-side functionality aside from what I have determined will be processed on the server.