Skip to content

Commit

Permalink
save actions to redis
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisrude committed Aug 15, 2023
1 parent abc4b41 commit bfc259d
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 138 deletions.
1 change: 0 additions & 1 deletion drumline-client/src/lib/stores/game_state_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ const fn_handle_received_action: SolveClientCallback = (
if (null === game_state) {
return null;
}
console.log('Action from server: ', action);
game_state.apply(action);

// directly reapply all our pending actions...
Expand Down
16 changes: 12 additions & 4 deletions drumline-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ import process from 'node:process';
import cors, { CorsOptions } from 'cors';
import express from 'express';
import http from 'http';
import { RedisClientType, createClient } from 'redis';
import { PuzzleCrudder } from './express';
import { PuzzleRedisClient } from './redis';
import { CORS_ALLOW_URL, PORT, SECRET_REDIS_URL } from './secrets';
import { EchoServer } from './websockets';

const redis_client = new PuzzleRedisClient(
SECRET_REDIS_URL,
);
console.log(`using redis url: `, SECRET_REDIS_URL);
const client: RedisClientType = createClient({
url: SECRET_REDIS_URL,
socket: {
connectTimeout: 50000,
},
});


const redis_client = new PuzzleRedisClient(client);
await redis_client.connect();

const cors_options: CorsOptions = {
Expand All @@ -23,7 +31,7 @@ app.use(cors(cors_options));
const puzzle_crudder = new PuzzleCrudder(app, redis_client);

const http_server = http.createServer(app);
const ws_server = new EchoServer();
const ws_server = new EchoServer(client);

http_server.on('upgrade', function upgrade(request, socket, head) {
ws_server.handleUpgrade(request, socket, head, function done(ws) {
Expand Down
14 changes: 3 additions & 11 deletions drumline-server/src/redis/puzzle_redis_client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Puzzle, PuzzleListInfo } from '@chrisrude/drumline-lib';
import { RedisClientType, createClient } from 'redis';
import { RedisClientType } from 'redis';
import { ADMIN_USER_UUIDS } from '../secrets';

const RESULT_OK = 'OK';
Expand All @@ -17,16 +17,8 @@ export { PuzzleRedisClient };
class PuzzleRedisClient {
private readonly _client: RedisClientType;

constructor(
url: string | undefined,
) {
console.log(`using redis url: `, url);
this._client = createClient({
url,
socket: {
connectTimeout: 50000,
},
});
constructor(client: RedisClientType) {
this._client = client
}

connect = async (): Promise<void> => {
Expand Down
63 changes: 63 additions & 0 deletions drumline-server/src/redis/solve_redis_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { GameActions, actionToString } from '@chrisrude/drumline-lib';
import { RedisClientType } from 'redis';

export { SolveRedisClient };

const ACTION_LIST_KEY_SUFFIX = 'actions';

// const CURRENT_STATE_SUBKEY = 'state';
// const CURRENT_STATE_SUBKEY = 'solved';

class SolveRedisClient {

private readonly _client: RedisClientType;

constructor(client: RedisClientType) {
this._client = client;
}

connect = async (): Promise<void> => {
await this._client.connect();
};

disconnect = (): Promise<void> => {
return this._client.disconnect();
};

get_solve_actions = async (
solve_id: string,
start_offset: number,
end_offset: number = -1,
): Promise<string[]> => {
const list_key = this._action_list_key(solve_id);
return await this._client.lRange(list_key, start_offset, end_offset)
};

// takes an action and appends it to the list of actions for the given solve
// note that we may have already seen this action before, so we need to
// check for that and ignore it if we have
// when we do store it, we do not set the change_count. Instead,
// we just let it to in at whatever place it winds up, and then
// set the change_count value when we retrieve it.
add_solve_action = async (solve_id: string, action: GameActions): Promise<number> => {

if (action.change_count !== -1) {
// the client is claiming that this has already been
// stored by the server. In the future we might handle
// this to help the server recover from a data loss, but
// for now just ignore it.
return -1;
}

const list_key = this._action_list_key(solve_id);
const str_action = actionToString(action);
return await this._client.rPush(
list_key,
str_action,
);
};

_solve_id_key = (solve_id: string): string => `solve:${solve_id}`;

_action_list_key = (solve_id: string): string => `${this._solve_id_key(solve_id)}:${ACTION_LIST_KEY_SUFFIX}`;
}
107 changes: 107 additions & 0 deletions drumline-server/src/websockets/client_state_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { UserId } from "@chrisrude/drumline-lib";
import { WebSocket } from 'ws';

export { ClientStateStore };

class ClientsBySolve {
private readonly _clients_by_solve: Map<string, Set<WebSocket>>;

constructor() {
this._clients_by_solve = new Map();
}

add = (ws: WebSocket, solve_id: string) => {
const clients = this._clients_by_solve.get(solve_id);
if (clients) {
clients.add(ws);
} else {
this._clients_by_solve.set(solve_id, new Set([ws]));
}
}

remove = (ws: WebSocket, solve_id: string) => {
const clients = this._clients_by_solve.get(solve_id);
if (clients) {
clients.delete(ws);
if (clients.size === 0) {
this._clients_by_solve.delete(solve_id);
}
}
}

get = (solve_id: string): Set<WebSocket> => {
return this._clients_by_solve.get(solve_id) || new Set();
}
}

class ClientState {
solve_id: string | null;
user_id: UserId | null;

constructor() {
this.solve_id = null;
this.user_id = null;
}
}

class ClientStateStore {
readonly client_states: Map<WebSocket, ClientState>;
readonly clients_by_solve: ClientsBySolve;

constructor() {
this.client_states = new Map();
this.clients_by_solve = new ClientsBySolve();
}

num_clients = (): number => {
return this.client_states.size;
}

add_client = (ws: WebSocket): void => {
if (this.client_states.has(ws)) {
throw new Error('Client already exists');
}
this.client_states.set(ws, new ClientState());
}

add_to_solve = (ws: WebSocket, solve_id: string): void => {
const client_state = this.client_states.get(ws);
if (!client_state) {
throw new Error('Client does not exist');
}
client_state.solve_id = solve_id;
this.clients_by_solve.add(ws, solve_id);
}

remove_from_solve = (ws: WebSocket): void => {
const client_state = this.client_states.get(ws);
if (!client_state) {
throw new Error('Client does not exist');
}
if (client_state.solve_id) {
this.clients_by_solve.remove(ws, client_state.solve_id);
}
client_state.solve_id = null;
}

remove_client = (ws: WebSocket): void => {
const current_state = this.client_states.get(ws);
if (!current_state) {
throw new Error('Client does not exist');
}
this.remove_from_solve(ws);
this.client_states.delete(ws);
}

get_client_state = (ws: WebSocket): ClientState => {
const client_state = this.client_states.get(ws);
if (!client_state) {
throw new Error('Client does not exist');
}
return client_state;
}

get_clients_for_solve = (solve_id: string): Set<WebSocket> => {
return this.clients_by_solve.get(solve_id);
}
}
Loading

0 comments on commit bfc259d

Please sign in to comment.