mirror of
https://github.com/cyberarm/i-mic-fps.git
synced 2025-12-15 07:32:35 +00:00
Added rubocop config, more work on CyberarmEngine Netcode; basic sending and receiving of packets is now functional
This commit is contained in:
8
.rubocop.yml
Normal file
8
.rubocop.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Style/StringLiterals:
|
||||||
|
EnforcedStyle: double_quotes
|
||||||
|
|
||||||
|
Metrics/MethodLength:
|
||||||
|
Max: 40
|
||||||
|
|
||||||
|
Style/EmptyMethod:
|
||||||
|
EnforcedStyle: expanded
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class IMICFPS
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
MULTICAST_ADDRESS = "224.0.0.1"
|
MULTICAST_ADDRESS = "224.0.0.1"
|
||||||
MULTICAST_PORT = 30_000
|
MULTICAST_PORT = 30_000
|
||||||
@@ -6,7 +6,7 @@ class IMICFPS
|
|||||||
REMOTE_GAMEHUB = "i-mic.cyberarm.dev"
|
REMOTE_GAMEHUB = "i-mic.cyberarm.dev"
|
||||||
REMOTE_GAMEHUB_PORT = 98765
|
REMOTE_GAMEHUB_PORT = 98765
|
||||||
|
|
||||||
DEFAULT_SERVER_HOST = "0.0.0.0"
|
DEFAULT_SERVER_HOSTNAME = "0.0.0.0"
|
||||||
DEFAULT_SERVER_PORT = 56789
|
DEFAULT_SERVER_PORT = 56789
|
||||||
DEFAULT_SERVER_QUERY_PORT = 28900
|
DEFAULT_SERVER_QUERY_PORT = 28900
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
module CyberarmEngine
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
class Channel
|
class Channel
|
||||||
|
def initialize(id:, mode:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,31 +1,78 @@
|
|||||||
module CyberarmEngine
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
class Connection
|
class Connection
|
||||||
def initialize(hostname:, port:, max_clients:, channels: 1)
|
def initialize(hostname:, port:, channels: 3)
|
||||||
|
@hostname = hostname
|
||||||
|
@port = port
|
||||||
|
|
||||||
|
@channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) }
|
||||||
|
|
||||||
|
@peer = Peer.new(id: 0, hostname: "", port: "")
|
||||||
|
|
||||||
|
@last_read_time = Networking.milliseconds
|
||||||
|
@last_write_time = Networking.milliseconds
|
||||||
|
|
||||||
|
@total_packets_sent = 0
|
||||||
|
@total_packets_received = 0
|
||||||
|
@total_data_sent = 0
|
||||||
|
@total_data_received = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# Callbacks #
|
# Callbacks #
|
||||||
def connected
|
def connected
|
||||||
end
|
end
|
||||||
|
|
||||||
def disconnected(reason)
|
def disconnected(reason:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reconnected
|
def reconnected
|
||||||
end
|
end
|
||||||
|
|
||||||
def packet_received(message, channel)
|
def packet_received(message:, channel:)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Functions #
|
# Functions #
|
||||||
def send_packet(message, reliable, channel = 0)
|
def send_packet(message:, reliable: false, channel: 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def broadcast_packet(message, reliable, channel = 0)
|
def connect(timeout: Protocol::TIMEOUT_PERIOD)
|
||||||
|
@socket = UDPSocket.new
|
||||||
|
|
||||||
|
write(PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_CONNECT))
|
||||||
end
|
end
|
||||||
|
|
||||||
def disconnect(reason = "")
|
def disconnect(timeout: Protocol::TIMEOUT_PERIOD)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
while read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def read
|
||||||
|
data, addr = @socket.recvfrom_nonblock(Protocol::MAX_PACKET_SIZE)
|
||||||
|
pkt = PacketHandler.handle(host: self, raw: data, peer: @peer)
|
||||||
|
packet_received(message: pkt.message, channel: -1) if pkt.is_a?(RawPacket)
|
||||||
|
|
||||||
|
@total_packets_received += 1
|
||||||
|
@total_data_received += data.length
|
||||||
|
@last_read_time = Networking.milliseconds
|
||||||
|
|
||||||
|
return true
|
||||||
|
rescue IO::WaitReadable
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(packet)
|
||||||
|
raw = packet.encode
|
||||||
|
@socket.send(raw, 0, @hostname, @port)
|
||||||
|
|
||||||
|
@total_packets_sent += 1
|
||||||
|
@total_data_sent += raw.length
|
||||||
|
@last_write_time = Networking.milliseconds
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
module CyberarmEngine
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
class Packet
|
class Packet
|
||||||
attr_reader :protocol_version, :type, :peer_id, :message
|
attr_reader :protocol_version, :peer_id, :channel, :message
|
||||||
|
|
||||||
def self.type
|
def self.decode(raw)
|
||||||
raise NotImplementedError, "#{self.class}.type must be defined!"
|
header = raw.unpack(CyberarmEngine::Networking::Protocol::PACKET_BASE_HEADER)
|
||||||
|
|
||||||
|
Packet.new(protocol_version: header[0], peer_id: header[1], channel: header[2], message: raw[Protocol::PACKET_BASE_HEADER_LENGTH...raw.length])
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.decode(packet)
|
def initialize(protocol_version:, peer_id:, channel:, message:)
|
||||||
raise NotImplementedError, "#{self.class}.decode must be defined!"
|
@protocol_version = protocol_version
|
||||||
|
@peer_id = peer_id
|
||||||
|
@channel = channel
|
||||||
|
@message = message
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode
|
def encode
|
||||||
raise NotImplementedError, "#{self.class}#encode must be defined!"
|
header = [
|
||||||
|
@protocol_version,
|
||||||
|
@peer_id,
|
||||||
|
@channel
|
||||||
|
].pack(CyberarmEngine::Networking::Protocol::PACKET_BASE_HEADER)
|
||||||
|
|
||||||
|
"#{header}#{@message}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,108 @@
|
|||||||
module CyberarmEngine
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
module PacketHandler
|
module PacketHandler
|
||||||
|
def self.type_to_name(type:)
|
||||||
|
Protocol.constants.select { |const| const.to_s.start_with?("PACKET_") }
|
||||||
|
.find { |const| Protocol.const_get(const) == type }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.handle(host:, raw:, peer:)
|
||||||
|
packet = Packet.decode(raw)
|
||||||
|
type = packet.message.unpack1("C")
|
||||||
|
|
||||||
|
puts "#{host.class} received #{type_to_name(type: type)}"
|
||||||
|
|
||||||
|
case type
|
||||||
|
when Protocol::PACKET_CONTROL
|
||||||
|
handle_control_packet(host, packet, peer)
|
||||||
|
when Protocol::PACKET_RAW
|
||||||
|
handle_raw_packet(packet)
|
||||||
|
else
|
||||||
|
raise NotImplementedError, "A Packet handler for #{type} is not implmented!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.handle_control_packet(host, packet, peer)
|
||||||
|
pkt = ControlPacket.decode(packet.message)
|
||||||
|
|
||||||
|
case pkt.control_type
|
||||||
|
when Protocol::CONTROL_CONNECT # TOSERVER only
|
||||||
|
if (peer_id = host.available_peer_id)
|
||||||
|
peer.id = peer_id
|
||||||
|
host.clients << peer
|
||||||
|
peer.write_queue << create_control_packet(peer: peer, control_type: Protocol::CONTROL_SET_PEER_ID, message: [peer_id].pack("n"))
|
||||||
|
host.client_connected(peer: peer)
|
||||||
|
else
|
||||||
|
host.write(
|
||||||
|
peer: peer,
|
||||||
|
packet: PacketHandler.create_control_packet(
|
||||||
|
peer: peer,
|
||||||
|
control_type: Protocol::CONTROL_DISCONNECT,
|
||||||
|
message: "ERROR: max number of clients already connected"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
when Protocol::CONTROL_SET_PEER_ID # TOCLIENT only
|
||||||
|
peer.id = pkt.message.unpack1("n")
|
||||||
|
host.connected
|
||||||
|
|
||||||
|
when Protocol::CONTROL_DISCONNECT
|
||||||
|
if host.is_a?(Server)
|
||||||
|
host.client_disconnected(peer: peer)
|
||||||
|
else
|
||||||
|
host.disconnected(reason: pkt.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
when Protocol::CONTROL_HEARTBEAT
|
||||||
|
when Protocol::CONTROL_PING
|
||||||
|
when Protocol::CONTROL_PONG
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.handle_raw_packet(packet)
|
||||||
|
RawPacket.decode(packet.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_control_packet(peer:, control_type:, message: nil, reliable: false, channel: 0)
|
||||||
|
message_packet = nil
|
||||||
|
|
||||||
|
if reliable
|
||||||
|
warn "Reliable packets are not yet implemented!"
|
||||||
|
packet = ControlPacket.new(control_type: control_type, message: message)
|
||||||
|
message_packet = ReliablePacket.new(sequence_number: peer.next_reliable_sequence_number, message: packet.encode)
|
||||||
|
else
|
||||||
|
message_packet = ControlPacket.new(control_type: control_type, message: message)
|
||||||
|
end
|
||||||
|
|
||||||
|
Packet.new(
|
||||||
|
protocol_version: Protocol::PROTOCOL_VERSION,
|
||||||
|
peer_id: peer.id,
|
||||||
|
channel: channel,
|
||||||
|
message: message_packet.encode
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_raw_packet(peer:, message:, reliable: false, channel: 0)
|
||||||
|
message_packet = nil
|
||||||
|
|
||||||
|
if reliable
|
||||||
|
warn "Reliable packets are not yet implemented!"
|
||||||
|
packet = RawPacket.new(message: message)
|
||||||
|
message_packet = ReliablePacket.new(sequence_number: peer.next_reliable_sequence_number, message: packet.encode)
|
||||||
|
else
|
||||||
|
message_packet = RawPacket.new(message: message)
|
||||||
|
end
|
||||||
|
|
||||||
|
Packet.new(
|
||||||
|
protocol_version: Protocol::PROTOCOL_VERSION,
|
||||||
|
peer_id: peer.id,
|
||||||
|
channel: channel,
|
||||||
|
message: message_packet.encode
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
32
lib/networking/backend/packets/control_packet.rb
Normal file
32
lib/networking/backend/packets/control_packet.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module CyberarmEngine
|
||||||
|
module Networking
|
||||||
|
class ControlPacket
|
||||||
|
attr_reader :message, :type, :control_type
|
||||||
|
|
||||||
|
HEADER_PACKER = "CC"
|
||||||
|
HEADER_LENGTH = 1 + 1 # bytes
|
||||||
|
|
||||||
|
def self.decode(raw_message)
|
||||||
|
header = raw_message.unpack(HEADER_PACKER)
|
||||||
|
message = raw_message[HEADER_LENGTH..raw_message.length - 1]
|
||||||
|
|
||||||
|
ControlPacket.new(type: header[0], control_type: header[1], message: message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(control_type:, message: nil, type: Protocol::PACKET_CONTROL)
|
||||||
|
@type = type
|
||||||
|
@control_type = control_type
|
||||||
|
@message = message
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode
|
||||||
|
header = [
|
||||||
|
@type,
|
||||||
|
@control_type
|
||||||
|
].pack(HEADER_PACKER)
|
||||||
|
|
||||||
|
"#{header}#{@message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
module CyberarmEngine
|
|
||||||
module Networking
|
|
||||||
class DataPacket < Packet
|
|
||||||
HEADER_PACKER = "CCn"
|
|
||||||
HEADER_LENGTH = 1 + 1 + 4 # bytes
|
|
||||||
|
|
||||||
def self.type
|
|
||||||
Protocol::DATA
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.decode(raw_message)
|
|
||||||
header = raw_message.unpack(HEADER_PACKER)
|
|
||||||
message = raw_message[HEADER_LENGTH..raw_message.length - 1]
|
|
||||||
|
|
||||||
DataPacket.new(protocol_version: header[0], type: header[1], message: message)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(protocol_version:, type:, peer_id:, message:)
|
|
||||||
@protocol_version = protocol_version
|
|
||||||
@type = type
|
|
||||||
@peer_id = peer_id
|
|
||||||
|
|
||||||
@message = message
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode
|
|
||||||
header = [
|
|
||||||
Protocol::PROTOCOL_VERSION,
|
|
||||||
@type,
|
|
||||||
@peer_id,
|
|
||||||
].pack(HEADER_PACKER)
|
|
||||||
|
|
||||||
"#{header}#{message}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
0
lib/networking/backend/packets/frament_packet.rb
Normal file
0
lib/networking/backend/packets/frament_packet.rb
Normal file
30
lib/networking/backend/packets/raw_packet.rb
Normal file
30
lib/networking/backend/packets/raw_packet.rb
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
module CyberarmEngine
|
||||||
|
module Networking
|
||||||
|
class RawPacket
|
||||||
|
attr_reader :message, :type
|
||||||
|
|
||||||
|
HEADER_PACKER = "C"
|
||||||
|
HEADER_LENGTH = 1 # bytes
|
||||||
|
|
||||||
|
def self.decode(raw_message)
|
||||||
|
header = raw_message.unpack(HEADER_PACKER)
|
||||||
|
message = raw_message[HEADER_LENGTH..raw_message.length - 1]
|
||||||
|
|
||||||
|
RawPacket.new(type: header[0], message: message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(message:, type: Protocol::PACKET_RAW)
|
||||||
|
@type = type
|
||||||
|
@message = message
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode
|
||||||
|
header = [
|
||||||
|
@type
|
||||||
|
].pack(HEADER_PACKER)
|
||||||
|
|
||||||
|
"#{header}#{@message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
32
lib/networking/backend/packets/reliable_packet.rb
Normal file
32
lib/networking/backend/packets/reliable_packet.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module CyberarmEngine
|
||||||
|
module Networking
|
||||||
|
class ReliablePacket
|
||||||
|
attr_reader :message, :type, :control_type
|
||||||
|
|
||||||
|
HEADER_PACKER = "Cn"
|
||||||
|
HEADER_LENGTH = 1 + 2 # bytes
|
||||||
|
|
||||||
|
def self.decode(raw_message)
|
||||||
|
header = raw_message.unpack(HEADER_PACKER)
|
||||||
|
message = raw_message[HEADER_LENGTH..raw_message.length - 1]
|
||||||
|
|
||||||
|
ReliablePacket.new(type: header[0], control_type: header[1], message: message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(sequence_number:, message:, type: Protocol::PACKET_RELIABLE)
|
||||||
|
@type = type
|
||||||
|
@sequence_number = sequence_number
|
||||||
|
@message = message
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode
|
||||||
|
header = [
|
||||||
|
@type,
|
||||||
|
@control_type
|
||||||
|
].pack(HEADER_PACKER)
|
||||||
|
|
||||||
|
"#{header}#{@message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,14 +1,34 @@
|
|||||||
module CyberarmEngine
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
class Peer
|
class Peer
|
||||||
attr_reader :id, :hostname, :port, :data
|
attr_reader :id, :hostname, :port, :data, :read_queue, :write_queue
|
||||||
|
attr_accessor :total_packets_sent, :total_packets_received,
|
||||||
|
:total_data_sent, :total_data_received,
|
||||||
|
:last_read_time, :last_write_time
|
||||||
|
|
||||||
def initialize(id:, hostname:, port:)
|
def initialize(id:, hostname:, port:)
|
||||||
@id = id
|
@id = id
|
||||||
@hostname = hostname
|
@hostname = hostname
|
||||||
@port = port
|
@port = port
|
||||||
|
|
||||||
@data = {}
|
@data = {}
|
||||||
|
@read_queue = []
|
||||||
|
@write_queue = []
|
||||||
|
|
||||||
|
@last_read_time = Networking.milliseconds
|
||||||
|
@last_write_time = Networking.milliseconds
|
||||||
|
|
||||||
|
@total_packets_sent = 0
|
||||||
|
@total_packets_received = 0
|
||||||
|
@total_data_sent = 0
|
||||||
|
@total_data_received = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def id=(n)
|
||||||
|
raise "Peer id must be an integer" unless n.is_a?(Integer)
|
||||||
|
|
||||||
|
@id = n
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
module CyberarmEngine
|
module CyberarmEngine
|
||||||
module Networking
|
module Networking
|
||||||
module Protocol
|
module Protocol
|
||||||
MAX_PACKET_SIZE = 1024
|
MAX_PACKET_SIZE = 1024 # bytes
|
||||||
PROTOCOL_VERSION = 0 # int
|
PROTOCOL_VERSION = 0 # u32
|
||||||
HEARTBEAT_INTERVAL = 5_000 # ms
|
HEARTBEAT_INTERVAL = 5_000 # ms
|
||||||
TIMEOUT_PERIOD = 30_000 # ms
|
TIMEOUT_PERIOD = 30_000 # ms
|
||||||
|
|
||||||
packet_types = %w{
|
PACKET_BASE_HEADER = "NnC" # protocol version (u32), sender peer id (u16), channel (u8)
|
||||||
# protocol packets
|
PACKET_BASE_HEADER_LENGTH = 4 + 2 + 1 # bytes
|
||||||
reliable
|
|
||||||
multipart
|
|
||||||
control
|
|
||||||
data
|
|
||||||
|
|
||||||
# control packet types
|
# protocol packets
|
||||||
disconnect
|
PACKET_RELIABLE = 0
|
||||||
acknowledge
|
PACKET_FRAGMENT = 1
|
||||||
heartbeat
|
PACKET_CONTROL = 2
|
||||||
ping
|
PACKET_RAW = 3
|
||||||
}
|
|
||||||
|
|
||||||
# emulate c-like enum
|
# control packet types
|
||||||
packet_types.each_with_index do |type, i|
|
CONTROL_CONNECT = 30
|
||||||
next if type.start_with?("#")
|
CONTROL_SET_PEER_ID = 31
|
||||||
self.const_set(:"#{type.upcase}", i)
|
CONTROL_DISCONNECT = 32
|
||||||
end
|
CONTROL_ACKNOWLEDGE = 33
|
||||||
|
CONTROL_HEARTBEAT = 34
|
||||||
|
CONTROL_PING = 35
|
||||||
|
CONTROL_PONG = 36
|
||||||
|
CONTROL_SET_PEER_MTU = 37 # In future
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,59 +1,180 @@
|
|||||||
class Server
|
module CyberarmEngine
|
||||||
attr_reader :hostname, :port, :max_clients
|
module Networking
|
||||||
def initialize(hostname: "0.0.0.0", port: 56789, max_clients: 32, channels: 1)
|
class Server
|
||||||
@hostname = hostname
|
attr_reader :hostname, :port, :max_clients
|
||||||
@port = port
|
attr_accessor :total_packets_sent, :total_packets_received,
|
||||||
@max_clients = max_clients
|
:total_data_sent, :total_data_received,
|
||||||
|
:last_read_time, :last_write_time
|
||||||
|
|
||||||
@socket = UDPSocket.new
|
def initialize(
|
||||||
@socket.bind(@hostname, @port)
|
hostname: CyberarmEngine::Networking::DEFAULT_SERVER_HOSTNAME,
|
||||||
|
port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT,
|
||||||
|
max_clients: CyberarmEngine::Networking::DEFAULT_PEER_LIMIT,
|
||||||
|
channels: 3
|
||||||
|
)
|
||||||
|
@hostname = hostname
|
||||||
|
@port = port
|
||||||
|
@max_clients = max_clients
|
||||||
|
|
||||||
@channels = Array(0..channels).map { |id| Channel.new(id: id, server: self) }
|
@channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) }
|
||||||
@peers = []
|
@peers = []
|
||||||
|
|
||||||
|
@last_read_time = Networking.milliseconds
|
||||||
|
@last_write_time = Networking.milliseconds
|
||||||
|
|
||||||
|
@total_packets_sent = 0
|
||||||
|
@total_packets_received = 0
|
||||||
|
@total_data_sent = 0
|
||||||
|
@total_data_received = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helpers #
|
||||||
|
def connected_clients
|
||||||
|
@peers.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def clients
|
||||||
|
@peers
|
||||||
|
end
|
||||||
|
|
||||||
|
# Callbacks #
|
||||||
|
|
||||||
|
# Called when client connects
|
||||||
|
def client_connected(peer:)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Called when client times out or explicitly disconnects
|
||||||
|
def client_disconnected(peer:, reason:)
|
||||||
|
end
|
||||||
|
|
||||||
|
### REMOVE? ###
|
||||||
|
# Called when client was not sending heartbeats or regular packets for a
|
||||||
|
# period of time, but was not logically disconnected and removed, and started
|
||||||
|
# send packets again.
|
||||||
|
#
|
||||||
|
# TLDR: Client was temporarily unreachable but did not timeout.
|
||||||
|
def client_reconnected(peer:)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Called when a (logical) packet is received from client
|
||||||
|
def packet_received(peer:, message:, channel:)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Functions #
|
||||||
|
# Bind server
|
||||||
|
def bind
|
||||||
|
# TODO: Handle socket errors
|
||||||
|
@socket = UDPSocket.new
|
||||||
|
@socket.bind(@hostname, @port)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Send packet to specified peer
|
||||||
|
def send_packet(peer:, message:, reliable: false, channel: 0)
|
||||||
|
if (peer = @peers.get(peer))
|
||||||
|
packet = PacketHandler.create_raw_packet(message, reliable, channel)
|
||||||
|
peer.write_queue << packet
|
||||||
|
else
|
||||||
|
# TODO: Handle no such peer error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Send packet to all connected peer
|
||||||
|
def broadcast_packet(message:, reliable: false, channel: 0)
|
||||||
|
@peers.each { |peer| send_packet(peer.id, message, reliable, channel) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Disconnect peer
|
||||||
|
def disconnect_client(peer:, reason: "")
|
||||||
|
if (peer = @peers.get(peer))
|
||||||
|
packet = PacketHandler.create_disconnect_packet(peer.id, reason)
|
||||||
|
peer.write_now!(packet)
|
||||||
|
@peers.delete(peer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
while(read)
|
||||||
|
end
|
||||||
|
|
||||||
|
# handle write queue
|
||||||
|
# TODO: handle reliable packets differently
|
||||||
|
@peers.each do |peer|
|
||||||
|
if Networking.milliseconds - peer.last_read_time > Protocol::TIMEOUT_PERIOD
|
||||||
|
message = "ERROR: connection timed out"
|
||||||
|
|
||||||
|
write(
|
||||||
|
peer: peer,
|
||||||
|
packet: PacketHandler.create_control_packet(
|
||||||
|
peer: peer,
|
||||||
|
control_type: Protocol::CONTROL_DISCONNECT,
|
||||||
|
message: message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
client_disconnected(peer: peer, reason: message)
|
||||||
|
@peers.delete(peer)
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
while(packet = peer.write_queue.shift)
|
||||||
|
write(peer: peer, packet: packet)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# !--- this following functions are meant for internal use only ---! #
|
||||||
|
|
||||||
|
def available_peer_id
|
||||||
|
peer_ids = @peers.map { ||peer| peer.id }
|
||||||
|
ids = (1..@max_clients).to_a - peer_ids
|
||||||
|
|
||||||
|
ids.size.positive? ? ids.first : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def read
|
||||||
|
data, addr = @socket.recvfrom_nonblock(Protocol::MAX_PACKET_SIZE)
|
||||||
|
peer = nil
|
||||||
|
|
||||||
|
if (peer = @peers.find { |pr| pr.hostname == addr[2] && pr.port == addr[1] })
|
||||||
|
pkt = PacketHandler.handle(host: self, raw: data, peer: peer)
|
||||||
|
packet_received(peer: peer, message: pkt.message, channel: 0) if pkt.is_a?(RawPacket)
|
||||||
|
else
|
||||||
|
# TODO: Reject packet unless it's a connection request
|
||||||
|
peer = Peer.new(id: 0, hostname: addr[2], port: addr[1])
|
||||||
|
pkt = PacketHandler.handle(host: self, raw: data, peer: peer)
|
||||||
|
|
||||||
|
if pkt && !pkt.is_a?(ControlPacket) && pkt.control_type != Protocol::CONTROL_CONNECT
|
||||||
|
write(
|
||||||
|
peer: peer,
|
||||||
|
packet: PacketHandler.create_control_packet(
|
||||||
|
peer: peer,
|
||||||
|
control_type: Protocol::CONTROL_DISCONNECT,
|
||||||
|
message: "ERROR: client not connected"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@total_packets_received += 1
|
||||||
|
@total_data_received += data.length
|
||||||
|
@last_read_time = Networking.milliseconds
|
||||||
|
|
||||||
|
true
|
||||||
|
rescue IO::WaitReadable
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(peer:, packet:)
|
||||||
|
raw = packet.encode
|
||||||
|
@socket.send(raw, 0, peer.hostname, peer.port)
|
||||||
|
|
||||||
|
@total_packets_sent += 1
|
||||||
|
@total_data_sent += raw.length
|
||||||
|
@last_write_time = Networking.milliseconds
|
||||||
|
|
||||||
|
peer.total_packets_sent += 1
|
||||||
|
peer.total_data_sent += raw.length
|
||||||
|
peer.last_write_time = Networking.milliseconds
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
# Helpers #
|
|
||||||
def connected_clients
|
|
||||||
@peers.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def clients
|
|
||||||
@peers
|
|
||||||
end
|
|
||||||
|
|
||||||
# Callbacks #
|
|
||||||
|
|
||||||
# Called when client connects
|
|
||||||
def client_connected(peer)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Called when client times out or explicitly disconnects
|
|
||||||
def client_disconnected(peer, reason)
|
|
||||||
end
|
|
||||||
|
|
||||||
### REMOVE? ###
|
|
||||||
# Called when client was not sending heartbeats or regular packets for a
|
|
||||||
# period of time, but was not logically disconnected and removed, and started
|
|
||||||
# send packets again.
|
|
||||||
#
|
|
||||||
# TLDR: Client was temporarily unreachable but did not timeout.
|
|
||||||
def client_reconnected(peer)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Called when a (logical) packet is received from client
|
|
||||||
def packet_received(peer, message, channel = 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Functions #
|
|
||||||
# Send packet to specified peer
|
|
||||||
def send_packet(peer, message, reliable, channel = 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Send packet to all connected peer
|
|
||||||
def broadcast_packet(message, reliable, channel = 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Disconnect peer
|
|
||||||
def disconnect_client(peer, reason = "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|||||||
66
new_server_test.rb
Normal file
66
new_server_test.rb
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
def require_all(directory)
|
||||||
|
files = Dir["#{directory}/**/*.rb"].sort!
|
||||||
|
|
||||||
|
begin
|
||||||
|
failed = []
|
||||||
|
first_name_error = nil
|
||||||
|
|
||||||
|
files.each do |file|
|
||||||
|
begin
|
||||||
|
require_relative file
|
||||||
|
rescue NameError => name_error
|
||||||
|
failed << file
|
||||||
|
first_name_error ||= name_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if failed.size == files.size
|
||||||
|
raise first_name_error
|
||||||
|
else
|
||||||
|
files = failed
|
||||||
|
end
|
||||||
|
end until( failed.empty? )
|
||||||
|
end
|
||||||
|
|
||||||
|
require "socket"
|
||||||
|
require_relative "lib/networking"
|
||||||
|
require_all "lib/networking/backend"
|
||||||
|
|
||||||
|
server = CyberarmEngine::Networking::Server.new
|
||||||
|
def server.client_connected(peer:)
|
||||||
|
puts "Client connected as peer: #{peer.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def server.packet_received(peer:, message:, channel:)
|
||||||
|
pp "Server received: #{message} [on channel: #{channel} from peer: #{peer&.id}]"
|
||||||
|
end
|
||||||
|
|
||||||
|
Thread.new do
|
||||||
|
server.bind
|
||||||
|
|
||||||
|
loop do
|
||||||
|
server.update
|
||||||
|
sleep (1000.0 / 60.0) / 10.0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
connection = CyberarmEngine::Networking::Connection.new(hostname: "localhost", port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT, channels: 3)
|
||||||
|
def connection.connected
|
||||||
|
puts "Connection: Connected!"
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection.disconnected(reason:)
|
||||||
|
puts "Connection: disconnected: #{reason}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection.packet_received(message:, channel:)
|
||||||
|
pp "Connection received: #{message} [on channel: #{channel} from peer: SERVER]"
|
||||||
|
end
|
||||||
|
connection.connect(timeout: 1_000)
|
||||||
|
|
||||||
|
loop do
|
||||||
|
connection.update
|
||||||
|
sleep (1000.0 / 60.0) / 10.0
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep
|
||||||
Reference in New Issue
Block a user