Class: Doom::Net::Client

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

Overview

The client half of the authoritative-server game. Talks only to the server (outbound UDP, so NAT is a non-issue), joins by restoring a snapshot of the live world, then follows the server's finalized frame stream -- applying joins, leaves and commands in tic order -- so its world stays byte-identical to the server's. It runs no simulation of its own and never predicts: the local player sees their own input after the round trip to the server and back, which is the price of a model where a dead phone can't stall anyone.

Everything is polled, never blocking, so the game keeps rendering while a join is still in flight or a resync is underway.

A join is a small state machine: HELLO -> WELCOME (who you are, which map) -> SNAPSHOT chunks (the world) -> playing. HELLO is resent until WELCOME arrives; if the snapshot never completes, or the frame stream leaves a gap too old for the server's redundancy to fill, the client asks again and the server sends a fresh snapshot at the current tic.

Constant Summary collapse

HELLO_RESEND_SECONDS =
0.25
SYNC_TIMEOUT_SECONDS =

No completed snapshot this long -> ask again

1.5
RESYNC_GAP_TICS =

If the newest frame we've heard of is this far past the one we still need, the missing one has aged out of the server's resend window and is gone for good -- only a fresh snapshot recovers us.

12

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host:, port:, sprites:, load_map:, sound: nil, clock: -> { Time.now }, transport: nil) ⇒ Client

load_map is a callable name -> Map::MapData; the client learns which map from WELCOME and has no other way to reach the WAD.



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/doom/net/client.rb', line 34

def initialize(host:, port:, sprites:, load_map:, sound: nil,
               clock: -> { Time.now }, transport: nil)
  @host = host
  @port = port
  @sprites = sprites
  @load_map = load_map
  @sound = sound
  @clock = clock
  @transport = transport || Transport.new
  reset_join
  @last_hello_at = nil
end

Instance Attribute Details

#local_idObject (readonly)

Returns the value of attribute local_id.



30
31
32
# File 'lib/doom/net/client.rb', line 30

def local_id
  @local_id
end

#synced_ticObject (readonly)

Returns the value of attribute synced_tic.



30
31
32
# File 'lib/doom/net/client.rb', line 30

def synced_tic
  @synced_tic
end

#worldObject (readonly)

Returns the value of attribute world.



30
31
32
# File 'lib/doom/net/client.rb', line 30

def world
  @world
end

Instance Method Details

#connected?Boolean

WELCOME received

Returns:

  • (Boolean)


48
# File 'lib/doom/net/client.rb', line 48

def connected? = !@local_id.nil?   # WELCOME received

#local_portObject



47
# File 'lib/doom/net/client.rb', line 47

def local_port = @transport.local_port

#playing?Boolean

snapshot restored, following frames

Returns:

  • (Boolean)


49
# File 'lib/doom/net/client.rb', line 49

def playing? = !@world.nil?        # snapshot restored, following frames

#pollObject

Pump the network: chase the join if not yet playing, then drain and apply whatever has arrived. Safe every frame.



53
54
55
56
57
# File 'lib/doom/net/client.rb', line 53

def poll
  send_hello_if_due
  @transport.poll.each { |msg, _host, _port| handle(msg) }
  resync_if_stuck
end

#quitObject



67
68
69
70
# File 'lib/doom/net/client.rb', line 67

def quit
  send_packet(Protocol.encode_quit(@local_id)) if @local_id && !@transport.closed?
  @transport.close
end

#send_input(pairs) ⇒ Object

Send this frame's input for future tics. pairs is [[tic, Ticcmd], ...]; recent inputs are repeated by the caller so a lost packet is covered.



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

def send_input(pairs)
  return unless @local_id

  send_packet(Protocol.encode_input(@local_id, pairs))
end