mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 18:37:07 +02:00
Game
- Player distance (Settings > Interface, "player-distance"): how far
away another player is still built as a skater, 50 to 1000 m, default
120. Past it they keep their nametag or dot. All the way up shows
everyone. A player already shown stays until 15% further out, so one
skating along the edge is not built and taken down over and over. The
game ran a full skater for every player on the server wherever they
were, which is most of a frame on a full one
- Nametags are always ReSkate's own. The "ReSkate nametags" toggle, the
"Nametag style" button and the nametag-style command are gone; the
game's own nametags are never shown
- Nametag distance ("nametag-distance"): how far away a name still
shows before it becomes a dot, 10 to 500 m. Default 120, was a fixed
150, so by default the name and the skater go at the same distance
- Nametag dots ("nametag-dots"): off hides the dots for far players and
the ones at the screen's edge for players off screen
- Friends' nametags only ("nametags-friends"): only Steam friends get a
name or dot; chat bubbles still show for everyone
Server
- A player out of sight of another (half as far again as the low rate
starts, 255 m by default) is sent to them once a second instead of
five times. On a full server most players are out of each other's
sight, and at five a second each they were most of what was sent
- Rotations are rounded before a pose is passed on, more coarsely the
further away the recipient is: 0.04 degrees within the full rate's
reach, 0.16 within the half rate's, a third of a degree past that,
about 5 degrees out of sight. The pose is still an ordinary one. A
bone that turned by less than a step is the same bytes as before, so
the difference from the reference leaves it out, and the low bits of
the ones that did turn pack away. By distance only: a player the
crowd limit slowed keeps the fine rotations
- A recipient crossing from one precision to another is sent a whole
state of that player, since a difference from a reference of another
precision would carry every bone again
- "net" shows the size of the poses sent at each precision
Profiler
- A function in ReSkate.dll is reported by its address when the DLL has
no symbols. A release build named every one of ours as the nearest of
three exports, so a report could not say which was slow; the
release's symbols name the address afterwards
83 lines
3.7 KiB
C++
83 lines
3.7 KiB
C++
#pragma once
|
|
#include "wire_codec.h"
|
|
#include <map>
|
|
#include <tuple>
|
|
|
|
namespace dingosdk::multiplayer {
|
|
using StreamKey = std::pair<std::uint64_t, PacketKind>;
|
|
struct WireUpdate {
|
|
std::vector<std::uint8_t> bytes, baseline;
|
|
bool establishes_baseline() const { return !baseline.empty(); }
|
|
};
|
|
// Reference-encoded updates one broadcast already built for one packet encoding. A delta
|
|
// depends only on that encoding and on the reference snapshot (its sequence and exact
|
|
// bytes), so a later recipient holding the same reference gets the same bytes without
|
|
// another patch and compression. References point into the senders: keep a cache only
|
|
// while no sender records a send (DeltaSender::sent).
|
|
struct DeltaCache {
|
|
struct Entry {
|
|
std::uint32_t sequence{};
|
|
const std::vector<std::uint8_t> *reference{};
|
|
std::vector<std::uint8_t> bytes;
|
|
};
|
|
std::vector<Entry> entries;
|
|
};
|
|
// Every delta references a reliable full snapshot, never the previous delta.
|
|
// Failed/skipped sends do not advance the sender's reference state.
|
|
class DeltaSender {
|
|
struct Base {
|
|
std::vector<std::uint8_t> raw;
|
|
std::uint64_t epoch{}, world{}, time{}, touched{};
|
|
std::uint32_t sequence{};
|
|
};
|
|
std::map<StreamKey, Base> bases_;
|
|
std::uint64_t clock_{};
|
|
std::uint64_t refresh_us_ = 2000000;
|
|
bool aligned_{};
|
|
WireUpdate build(const Packet &, std::span<const std::uint8_t> raw, std::span<const std::uint8_t> wire,
|
|
DeltaCache *cache) const;
|
|
|
|
public:
|
|
WireUpdate prepare(const Packet &) const;
|
|
WireUpdate prepare(const Packet &, std::span<const std::uint8_t> raw,
|
|
std::span<const std::uint8_t> wire) const;
|
|
// The same result, reusing (and adding to) deltas built for other recipients of
|
|
// exactly this raw/wire encoding.
|
|
WireUpdate prepare(const Packet &, std::span<const std::uint8_t> raw, std::span<const std::uint8_t> wire,
|
|
DeltaCache &cache) const;
|
|
void sent(const Packet &, WireUpdate &&);
|
|
// Drops a stream's reference: the next packet of it goes as a whole state.
|
|
void forget(std::uint64_t source, PacketKind kind) { bases_.erase(StreamKey{source, kind}); }
|
|
// How old a pose or sound reference may get before a whole state replaces it. Each one
|
|
// is sent reliably, so Steam resends it until it arrives: a dedicated server, with a
|
|
// stream for every pair of players, keeps them rarer than the two seconds a game does.
|
|
// `aligned`: on the source's own clock, at each multiple of the time, instead of that
|
|
// long after this recipient's last one. Every recipient of a source then takes the same
|
|
// packet as its reference, and one patch serves them all (DeltaCache).
|
|
void set_refresh(std::uint64_t microseconds, bool aligned = false) {
|
|
refresh_us_ = microseconds;
|
|
aligned_ = aligned;
|
|
}
|
|
};
|
|
class DeltaReceiver {
|
|
struct Base {
|
|
std::vector<std::uint8_t> raw;
|
|
std::uint64_t epoch{}, world{}, session{}, map{};
|
|
std::uint32_t sequence{};
|
|
};
|
|
struct Stream {
|
|
std::deque<Base> bases;
|
|
std::uint64_t touched{};
|
|
};
|
|
std::map<StreamKey, Stream> streams_;
|
|
std::uint64_t clock_{};
|
|
|
|
public:
|
|
// Missing references are a recoverable dropped frame, not protocol failure.
|
|
std::optional<Packet> receive(std::span<const std::uint8_t>, bool &missing_reference,
|
|
std::uint64_t accepted_world = 0) noexcept;
|
|
// Whether a delta of this stream against baseline `sequence` of `epoch` could be decoded now.
|
|
bool holds(std::uint64_t source, PacketKind kind, std::uint64_t epoch, std::uint32_t sequence) const noexcept;
|
|
};
|
|
} // namespace dingosdk::multiplayer
|