Class: Doom::Net::Transport
- Inherits:
-
Object
- Object
- Doom::Net::Transport
- Defined in:
- lib/doom/net/transport.rb
Overview
Non-blocking UDP for both multiplayer stacks: the peer-to-peer lockstep mesh (send_to/broadcast over the peer table) and the authoritative star server (send_to_addr, replying straight to whoever a packet came from).
UDP rather than TCP because a late ticcmd is worthless: head-of-line blocking would make one lost packet stall everything behind it, which is the opposite of what we want when the payload is already resent by the next packet anyway.
Nothing here retries or acknowledges. Loss is handled a layer up, by Lockstep resending recent commands, and ordering is handled by the tic numbers inside the packets.
Defined Under Namespace
Classes: Peer
Constant Summary collapse
- MAX_DATAGRAM =
2048
Instance Attribute Summary collapse
-
#peers ⇒ Object
readonly
Returns the value of attribute peers.
Instance Method Summary collapse
- #add_peer(host, port, player_id: nil) ⇒ Object
- #broadcast(bytes) ⇒ Object
- #close ⇒ Object
- #closed? ⇒ Boolean
-
#initialize(port: 0, host: '0.0.0.0') ⇒ Transport
constructor
A new instance of Transport.
- #local_port ⇒ Object
- #peer_for(host, port) ⇒ Object
-
#poll(max: 64) ⇒ Object
Drain what has arrived, decoded.
- #send_to(peer, bytes) ⇒ Object
-
#send_to_addr(host, port, bytes) ⇒ Object
Send to a bare address, without registering it as a peer.
Constructor Details
#initialize(port: 0, host: '0.0.0.0') ⇒ Transport
Returns a new instance of Transport.
30 31 32 33 34 |
# File 'lib/doom/net/transport.rb', line 30 def initialize(port: 0, host: '0.0.0.0') @socket = UDPSocket.new @socket.bind(host, port) @peers = {} end |
Instance Attribute Details
#peers ⇒ Object (readonly)
Returns the value of attribute peers.
28 29 30 |
# File 'lib/doom/net/transport.rb', line 28 def peers @peers end |
Instance Method Details
#add_peer(host, port, player_id: nil) ⇒ Object
40 41 42 43 44 |
# File 'lib/doom/net/transport.rb', line 40 def add_peer(host, port, player_id: nil) peer = Peer.new(host, port, player_id) @peers[peer.key] = peer peer end |
#broadcast(bytes) ⇒ Object
73 74 75 |
# File 'lib/doom/net/transport.rb', line 73 def broadcast(bytes) @peers.each_value { |peer| send_to(peer, bytes) } end |
#close ⇒ Object
101 102 103 |
# File 'lib/doom/net/transport.rb', line 101 def close @socket.close unless @socket.closed? end |
#closed? ⇒ Boolean
105 106 107 |
# File 'lib/doom/net/transport.rb', line 105 def closed? @socket.closed? end |
#local_port ⇒ Object
36 37 38 |
# File 'lib/doom/net/transport.rb', line 36 def local_port @socket.addr[1] end |
#peer_for(host, port) ⇒ Object
46 47 48 |
# File 'lib/doom/net/transport.rb', line 46 def peer_for(host, port) @peers["#{host}:#{port}"] end |
#poll(max: 64) ⇒ Object
Drain what has arrived, decoded. Returns [[message, host, port], ...].
max bounds the work per frame so a flood cannot starve rendering.
Undecodable packets are dropped silently: on an open port anyone can send anything, and it is not worth a log line per stray datagram.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/doom/net/transport.rb', line 82 def poll(max: 64) return [] if closed? = [] max.times do begin data, addr = @socket.recvfrom_nonblock(MAX_DATAGRAM) rescue IO::WaitReadable, Errno::EWOULDBLOCK, Errno::EAGAIN break rescue SystemCallError, IOError break end msg = Protocol.decode(data) << [msg, addr[3], addr[1]] if msg end end |
#send_to(peer, bytes) ⇒ Object
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/doom/net/transport.rb', line 50 def send_to(peer, bytes) return false if closed? @socket.send(bytes, 0, peer.host, peer.port) true rescue SystemCallError, IOError # A peer that has gone away must not take the session down with it; # the lockstep layer already notices them by stalling. false end |
#send_to_addr(host, port, bytes) ⇒ Object
Send to a bare address, without registering it as a peer. The game server tracks its clients itself and replies to wherever a packet came from, so it does not want the peer table.
64 65 66 67 68 69 70 71 |
# File 'lib/doom/net/transport.rb', line 64 def send_to_addr(host, port, bytes) return false if closed? @socket.send(bytes, 0, host, port) true rescue SystemCallError, IOError false end |