Session now runs only the room this screen is in. It plays ahead of the server with its own controls, guesses everyone else's, confirms ticks against the server's frames and rewinds on a wrong guess. It keeps its lead by playing a little faster or slower as the server says how early its controls arrive, resends an edit the server dropped, and checks the server's checksums. Gone with the peer-to-peer design: the host's decisions, the second arena game, the view swapping and spectating (to come back through the server). SAB_NET_STATS logs how it's keeping up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
4.1 KiB
C++
108 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include "input.h"
|
|
#include "sim.h"
|
|
#include <map>
|
|
#include <string>
|
|
|
|
struct Game;
|
|
|
|
constexpr int SESSION_RING = 64;
|
|
// A member's connection has no ping to show.
|
|
constexpr float PING_NONE = -2;
|
|
|
|
// One tick of the room as this screen played it: the state as it started (and its checksum), and once the tick's
|
|
// events were in, who played each fighter and the controls each was given.
|
|
struct SessionTick
|
|
{
|
|
Snapshot state;
|
|
Controls controls[MAX_FIGHTERS];
|
|
int owners[MAX_FIGHTERS];
|
|
Uint32 checksum = 0;
|
|
int tick = -1;
|
|
};
|
|
|
|
// The server's frame for a tick: every fighter's controls as it played them, after the events before them.
|
|
struct SessionFrame
|
|
{
|
|
Controls controls[MAX_FIGHTERS];
|
|
int count = 0;
|
|
int events = 0;
|
|
};
|
|
|
|
// Online play: this screen runs the room it's in (the lobby's sandbox, or a game queued in it), which the server runs
|
|
// too (see server/server.cpp). It plays ahead of the server, its own controls sent for the ticks it plays and everyone
|
|
// else's guessed to keep doing what they last did. The server's frames say what everyone really did: when one differs
|
|
// from what was played, the game rewinds to that tick and plays forward again (silently). It keeps its lead over the
|
|
// server by playing a tick more, or one less, now and then, as the server says how early its controls come.
|
|
class Session
|
|
{
|
|
public:
|
|
bool isActive = false;
|
|
bool isJoined = false;
|
|
bool isDesynced = false;
|
|
// Set when the room changes, for the camera to jump.
|
|
bool isViewChanged = false;
|
|
int rollbacks = 0;
|
|
int stalls = 0;
|
|
|
|
void update(Game& game, Controls local);
|
|
void stop(Game& game);
|
|
bool is_wanted(Game& game);
|
|
bool is_watching();
|
|
bool is_playing_arena(Game& game);
|
|
bool is_queued(Game& game);
|
|
void arena_leave(Game& game);
|
|
int fighter_get(Game& game, int member);
|
|
std::string base_name_get(Game& game, int member);
|
|
std::string name_get(Game& game, int member);
|
|
void chat_send(Game& game, const std::string& text);
|
|
void typing_send(Game& game, bool isTyping);
|
|
void kick(Game& game, const std::string& name, const std::string& reason);
|
|
int rename_wait_get();
|
|
void queue_send(Game& game, const Rules& rules, bool isOpen = true);
|
|
float ping_get(Game& game, int member);
|
|
|
|
private:
|
|
SessionTick ticks[SESSION_RING];
|
|
Controls own[SESSION_RING];
|
|
std::map<int, SessionFrame> frames;
|
|
// Each room's events by number, kept from before its state comes in case they come first.
|
|
std::map<int, std::map<int, RoomEvent>> events;
|
|
std::map<int, Uint32> checksums;
|
|
std::map<int, Controls> held;
|
|
// Every member's chosen name (cleaned), kept current by their renames, and the colour this screen last asked for.
|
|
std::map<int, std::string> names;
|
|
int room = 0;
|
|
// The next tick to play, the first not yet confirmed, and the earliest that has to be played again.
|
|
int tick = 0;
|
|
int confirmed = 0;
|
|
int rewindTo = -1;
|
|
// How early (in ticks) this screen's controls last reached the server, and when its pace last changed.
|
|
int lead = 0;
|
|
int paced = 0;
|
|
int sentColor = -1;
|
|
// When a newcomer gives up waiting for the WebRTC link, and whether its fighter has been brought in.
|
|
Uint64 linkDeadline = 0;
|
|
bool isProfileSent = false;
|
|
// With SAB_NET_STATS set, how it's keeping up goes to the log every few seconds.
|
|
bool isStatsLogged = false;
|
|
// Names can change only every RENAME_WAIT, so a player can't dodge being kicked by renaming: when this screen's can
|
|
// next, and whether it's told its pending one is waiting.
|
|
Uint64 nextRename = 0;
|
|
bool isRenameWaiting = false;
|
|
|
|
void start(Game& game);
|
|
void packets_take(Game& game);
|
|
void packet_handle(Game& game, const std::string& bytes);
|
|
void state_load(Game& game, PacketReader& reader, int serial, int at);
|
|
void frames_take(PacketReader& reader);
|
|
void confirm(Game& game);
|
|
void rewind(Game& game);
|
|
void step(Game& game, bool isLive);
|
|
void inputs_send(Game& game);
|
|
void local_find(Game& game);
|
|
void profile_watch(Game& game);
|
|
void text_send(Game& game, PacketType type, const std::string& text);
|
|
};
|