export interface DeepCopy { deepCopy(patch: Partial): T; } export interface Equals { equals(other: this): boolean; } function equals(a: T, b: T): boolean { if(typeof a === "string"){ return a == b; } if(typeof a === "number"){ return a == b; } return (a as Equals).equals(b as Equals); } interface LockstepProcessor { compareInput(a: Input, b: Input): boolean; cloneState(source: State): State; advanceState(state: State, input: Input): void; } export class LockstepState { private inputIndex = -1; private inputLog: Input[] = []; private canonIndex = -1; private canonState: State; private renderIndex = -1; private renderState: State; constructor(private initialState: State, private engine: LockstepProcessor) { this.canonState = engine.cloneState(initialState); this.renderState = engine.cloneState(initialState); } public addCanonInput(input: Input): void { this.canonIndex++; // advance canonical game state this.engine.advanceState(this.canonState, input); if(this.canonIndex <= this.renderIndex) { // we're rendering predicted states, so if the input changes we need to invalidate the rendered state if(!this.engine.compareInput(this.inputLog[this.canonIndex], input)) { this.renderState = this.engine.cloneState(this.canonState); this.renderIndex = this.canonIndex; } } this.inputLog[this.canonIndex] = input; } public addLocalInput(input: Input): void { this.inputIndex++; // ensure that we don't overwrite the canon input with local input somehow // (probably only possible in situations where game is unplayable anyways? but still for sanity.) if(this.inputIndex > this.canonIndex) { this.inputLog[this.inputIndex] = input; } } /** * Do any necessary simulation to catchup the predicted game state to the frame * that should be rendered, then return it. */ public getStateToRender(): State { // TODO: input lag by X frames const targetIndex = this.inputLog.length - 1; while(this.renderIndex < targetIndex) { this.renderIndex++; this.engine.advanceState(this.renderState, this.inputLog[this.renderIndex]); } return this.renderState; } } export class LockstepLoop> { }