diff --git a/plan.txt b/plan.txt
index 0c202c7..85476dc 100644
--- a/plan.txt
+++ b/plan.txt
@@ -1,11 +1,11 @@
Open:
-- Refactor input generics to distinct local/full types
- Multiplayer loopback server
- Insecured websocket server implementation
- Cloneable RNG that goes in state (use MurmurHash3 finalizer in counter mode?)
- remove all random() calls
Done:
+- Refactor input generics to distinct local/full types
- Refactor input messages for more than one player
- Rework State implementation for easier cloning/deserialization
- Test Lockstep/rollback
diff --git a/src/Ecs/Lockstep.ts b/src/Ecs/Lockstep.ts
index 711b76b..e1e78dd 100644
--- a/src/Ecs/Lockstep.ts
+++ b/src/Ecs/Lockstep.ts
@@ -1,28 +1,28 @@
export const INPUT_FREQUENCY = 33; // roughly 30fps
-export interface LockstepProcessor {
- compareInput(a: Input[], b: Input[]): boolean;
- predictInput(prev: Input[] | null, localPlayer: number, localInput: Input): Input[];
+export interface LockstepProcessor {
+ compareInput(a: GlobalInput, b: GlobalInput): boolean;
+ predictInput(prev: GlobalInput | null, localPlayer: number, localInput: LocalInput): GlobalInput;
cloneState(source: State): State;
- advanceState(state: State, input: Input[]): void;
+ advanceState(state: State, input: GlobalInput): void;
}
-export class LockstepState {
+export class LockstepState {
private inputIndex = -1;
- private inputLog: Input[][] = [];
+ private inputLog: GlobalInput[] = [];
private canonIndex = -1;
private canonState: State;
private renderIndex = -1;
private renderState: State;
- constructor(private initialState: State, private engine: LockstepProcessor) {
+ constructor(private initialState: State, private engine: LockstepProcessor) {
this.canonState = engine.cloneState(initialState);
this.renderState = engine.cloneState(initialState);
}
- public addCanonInput(input: Input[]): void {
+ public addCanonInput(input: GlobalInput): void {
this.canonIndex++;
// advance canonical game state
@@ -39,7 +39,7 @@ export class LockstepState {
}
/** Warning: this only supports one player input per frame. There is no support for two players using the same lockstep simulation instance. */
- public addLocalInput(player: number, input: Input): void {
+ public addLocalInput(player: number, input: LocalInput): void {
this.inputIndex++;
// ensure that we don't overwrite the canon input with local input somehow
@@ -66,13 +66,13 @@ export class LockstepState {
}
}
-export class Playback {
+export class Playback {
private frame = 0;
public constructor(
private state: State,
- private inputLog: Input[][],
- private engine: LockstepProcessor,
+ private inputLog: GlobalInput[],
+ private engine: LockstepProcessor,
) {}
public getNextState(): State {
diff --git a/src/Game/GameComponents.ts b/src/Game/GameComponents.ts
index a289019..e11a608 100644
--- a/src/Game/GameComponents.ts
+++ b/src/Game/GameComponents.ts
@@ -176,7 +176,7 @@ export class PlayerControl extends Component {
}
}
-export class Engine implements LockstepProcessor {
+export class Engine implements LockstepProcessor {
cloneState(old: Data) {
return new Data(old);
}
diff --git a/src/Game/Main.ts b/src/Game/Main.ts
index 28fb918..2f75099 100644
--- a/src/Game/Main.ts
+++ b/src/Game/Main.ts
@@ -11,7 +11,7 @@ import { Loopback } from "../Net/LoopbackServer";
import { Data, Engine, PlayerControl } from "./GameComponents";
import { Buttons } from "./Input";
-export class Main extends LockstepClient {
+export class Main extends LockstepClient {
buttons = new Buttons();
diff --git a/src/Net/LockstepClient.ts b/src/Net/LockstepClient.ts
index 43ea9df..02fddcb 100644
--- a/src/Net/LockstepClient.ts
+++ b/src/Net/LockstepClient.ts
@@ -35,29 +35,29 @@ export const enum MessageTypes {
export type Packet = { t: TypeId } & Payload;
-export type ClientMessage =
+export type ClientMessage =
| Packet }>
- | Packet
+ | Packet
| Packet
| Packet
;
-export type ServerMessage =
+export type ServerMessage =
| Packet }>
- | Packet
+ | Packet
| Packet
| Packet
;
-export type Server = Callbag, ServerMessage>;
+export type Server = Callbag, ServerMessage>;
-export abstract class LockstepClient {
+export abstract class LockstepClient {
private playerNumber = -1;
- private state: LockstepState;
+ private state: LockstepState;
public constructor(
- public readonly engine: LockstepProcessor,
+ public readonly engine: LockstepProcessor,
) {
const initialState = this.initState({});
this.state = new LockstepState(initialState, engine);
@@ -65,13 +65,13 @@ export abstract class LockstepClient {
public abstract initState(init: Partial): State;
- public abstract gatherInput(): Input;
+ public abstract gatherInput(): LocalInput;
/**
* Connect to a [perhaps emulated] server and return a disconnect callback
*/
- public connect(server: Server): () => void {
- let serverTalkback: Server | null = null;
+ public connect(server: Server): () => void {
+ let serverTalkback: Server | null = null;
const sampleInput = () => {
if (serverTalkback) {
@@ -85,15 +85,15 @@ export abstract class LockstepClient {
};
// connect to server
- server(0, (mode: number, data: Server | ServerMessage) => {
+ server(0, (mode: number, data: Server | ServerMessage) => {
if (mode == 0) {
- serverTalkback = data as Server;
+ serverTalkback = data as Server;
// kickoff input sender
setTimeout(sampleInput, INPUT_FREQUENCY);
} else if (mode == 1) {
// server message
- const message = data as ServerMessage;
+ const message = data as ServerMessage;
switch (message.t) {
case MessageTypes.SET_STATE:
const resetState = this.initState(message.s);
diff --git a/src/Net/LoopbackServer.ts b/src/Net/LoopbackServer.ts
index 323b858..fde9bde 100644
--- a/src/Net/LoopbackServer.ts
+++ b/src/Net/LoopbackServer.ts
@@ -2,17 +2,18 @@ import { Callbag } from "callbag";
import { ClientMessage, MessageTypes, ServerMessage } from "./LockstepClient";
-type Client = Callbag, ClientMessage>;
+type Client = Callbag, ClientMessage>;
-export function Loopback(start: number, data?: Client | ClientMessage) {
+/** Stub loopback server that handles a single client, for schemes where GlobalInput = LocalInput[] */
+export function Loopback(start: number, data?: Client | ClientMessage) {
if(start != 0) return;
- const sink = data as Client;
+ const sink = data as Client;
- sink(0, (type: number, data?: Client | ClientMessage) => {
+ sink(0, (type: number, data?: Client | ClientMessage) => {
if(type == 1) {
// message from client; just reflect for now
- const message = data as ClientMessage;
+ const message = data as ClientMessage;
switch(message.t) {
case MessageTypes.INPUT:
sink(1, {