Replace the stub loopback server with a local, but multi-client server.

This commit is contained in:
Tangent Wantwight 2020-05-02 21:14:15 -04:00
parent 633611056e
commit c8589828c7
2 changed files with 53 additions and 22 deletions

View file

@ -7,7 +7,7 @@ import { Location, Polygon, PolygonComponent, RenderBounds } from "../Ecs/Compon
import { Create } from "../Ecs/Data";
import { RunRenderBounds } from "../Ecs/Renderers";
import { LockstepClient } from "../Net/LockstepClient";
import { Loopback } from "../Net/LoopbackServer";
import { LoopbackServer } from "../Net/LoopbackServer";
import { Data, Engine, PlayerControl } from "./GameComponents";
import { Buttons } from "./Input";
@ -19,7 +19,9 @@ export class Main extends LockstepClient<KeyName[], KeyName[][], Data> {
super(new Engine());
keys.setHandler(this.buttons);
this.connect(Loopback);
const server = new LoopbackServer<KeyName[], Data>();
this.connect(server.socket);
server.resetState({});
pipe(
this.renderFrames,

View file

@ -1,28 +1,57 @@
import { Callbag } from "callbag";
import pipe from "callbag-pipe";
import share from "callbag-share";
import { ClientMessage, MessageTypes, ServerMessage } from "./LockstepClient";
import { INPUT_FREQUENCY } from "../Ecs/Lockstep";
import { catchTalkback, defer, interval, makeSubject, map, merge } from "../Utilities/Callbag";
import { ClientMessage, MessageTypes, Server, ServerMessage } from "./LockstepClient";
type Client<LocalInput, State> = Callbag<ServerMessage<LocalInput[], State>, ClientMessage<LocalInput, State>>;
/** Stub loopback server that handles a single client, for schemes where GlobalInput = LocalInput[] */
export function Loopback<LocalInput, State>(start: number, data?: Client<LocalInput, State> | ClientMessage<LocalInput, State>) {
if(start != 0) return;
/** Stub loopback server that handles multiple clients, for schemes where GlobalInput = LocalInput[] */
export class LoopbackServer<LocalInput, State> {
const sink = data as Client<LocalInput, State>;
private nextClientId = 0;
private inputBuffer: LocalInput[] = [];
sink(0, (type: number, data?: Client<LocalInput, State> | ClientMessage<LocalInput, State>) => {
if(type == 1) {
// message from client; just reflect for now
const message = data as ClientMessage<LocalInput, State>;
switch(message.t) {
case MessageTypes.INPUT:
sink(1, {
t: MessageTypes.INPUT,
i: [message.i],
});
break;
}
}
private heartbeat = pipe(
interval(INPUT_FREQUENCY),
map(() => ({
t: MessageTypes.INPUT,
i: this.inputBuffer.slice()
} as ServerMessage<LocalInput[], State>)),
);
private broadcast = makeSubject<ServerMessage<LocalInput[], State>>();
private serverFeed = share(merge(this.heartbeat, this.broadcast));
public readonly socket = defer(() => {
const playerNumber = this.nextClientId++;
return pipe(
this.serverFeed,
catchTalkback<ServerMessage<LocalInput[], State>, ClientMessage<LocalInput, State>>(message => {
switch (message.t) {
case MessageTypes.INPUT:
if (playerNumber >= 0) {
this.inputBuffer[playerNumber] = message.i;
}
break;
}
}),
map(message => {
if (message.t === MessageTypes.SET_STATE && playerNumber >= 0) {
return {
...message,
u: playerNumber,
};
} else {
return message;
}
})
);
});
sink(1, {t: MessageTypes.SET_STATE, u: 0, s: {}});
};
public resetState(newState: Partial<State>) {
this.broadcast(1, { t: MessageTypes.SET_STATE, u: -1, s: newState });
}
}