Class: Doom::Net::DesyncMonitor

Inherits:
Object
  • Object
show all
Defined in:
lib/doom/net/desync_monitor.rb

Overview

Watches for lockstep divergence.

Each peer fingerprints its world every CHECK_INTERVAL tics and broadcasts the result. When two peers report different fingerprints for the same tic, their simulations have already diverged and every tic after it is meaningless. The job here is to say so at the tic it happened, with enough detail to diagnose it, instead of letting the games drift apart while both players wonder why the other is shooting at nothing.

Peers exchange per-section hashes, not just the folded one: five extra numbers a second is nothing on the wire, and it turns "something diverged" into "the monsters diverged", which is the difference between an alarming report and a useful one.

Reports can arrive before or after the local check for the same tic, so both directions are buffered and compared whenever a pair completes.

Defined Under Namespace

Classes: Desync

Constant Summary collapse

CHECK_INTERVAL =

One second at DOOM's tic rate

35
HISTORY_TICS =

Keep ~10s; enough to pair up late reports

350

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(interval: CHECK_INTERVAL, &on_desync) ⇒ DesyncMonitor

on_desync is called with a Desync the moment one is detected.



36
37
38
39
40
41
42
# File 'lib/doom/net/desync_monitor.rb', line 36

def initialize(interval: CHECK_INTERVAL, &on_desync)
  @interval = interval
  @on_desync = on_desync
  @local = {}    # tic => section hashes
  @remote = {}   # tic => { peer_id => section hashes }
  @desyncs = []
end

Instance Attribute Details

#desyncsObject (readonly)

Returns the value of attribute desyncs.



33
34
35
# File 'lib/doom/net/desync_monitor.rb', line 33

def desyncs
  @desyncs
end

Instance Method Details

#check_due?(tic) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/doom/net/desync_monitor.rb', line 44

def check_due?(tic)
  (tic % @interval).zero?
end

#desynced?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/doom/net/desync_monitor.rb', line 66

def desynced?
  !@desyncs.empty?
end

#record(tic, world) ⇒ Object

Fingerprint the world if this tic is a checkpoint. Returns the section hashes to broadcast, or nil on a tic that is not checked.



50
51
52
53
54
55
56
57
58
# File 'lib/doom/net/desync_monitor.rb', line 50

def record(tic, world)
  return nil unless check_due?(tic)

  sections = world.state_hash_sections
  @local[tic] = sections
  compare(tic)
  prune(tic)
  sections
end

#remote_report(tic, peer_id, sections) ⇒ Object

Section hashes received from another peer.



61
62
63
64
# File 'lib/doom/net/desync_monitor.rb', line 61

def remote_report(tic, peer_id, sections)
  (@remote[tic] ||= {})[peer_id] = sections
  compare(tic)
end