Initial work on locales, more work on netcode

This commit is contained in:
2020-12-01 13:02:22 -06:00
parent b9c30ade80
commit c7590366a6
20 changed files with 131 additions and 47 deletions

View File

@@ -1,6 +1,7 @@
module CyberarmEngine
module Networking
class Connection
attr_reader :hostname, :port, :peer
def initialize(hostname:, port:, channels: 3)
@hostname = hostname
@port = port
@@ -8,14 +9,6 @@ module CyberarmEngine
@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
# Callbacks #
@@ -33,12 +26,13 @@ module CyberarmEngine
# Functions #
def send_packet(message:, reliable: false, channel: 0)
@peer.write_queue << PacketHandler.create_raw_packet(peer: @peer, message: message, reliable: reliable, channel: channel)
end
def connect(timeout: Protocol::TIMEOUT_PERIOD)
@socket = UDPSocket.new
write(PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_CONNECT))
write(packet: PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_CONNECT))
end
def disconnect(timeout: Protocol::TIMEOUT_PERIOD)
@@ -49,38 +43,36 @@ module CyberarmEngine
end
@peer.write_queue.reverse.each do |packet|
write(packet)
write(packet: packet)
@peer.write_queue.delete(packet)
end
if Networking.milliseconds - @last_write_time > Protocol::HEARTBEAT_INTERVAL
if Networking.milliseconds - @peer.last_write_time > Protocol::HEARTBEAT_INTERVAL
@peer.write_queue << PacketHandler.create_control_packet(peer: @peer, control_type: Protocol::CONTROL_HEARTBEAT)
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
@peer.total_packets_received += 1
@peer.total_data_received += data.length
@peer.last_read_time = Networking.milliseconds
return true
rescue IO::WaitReadable
return false
end
def write(packet)
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
@peer.total_packets_sent += 1
@peer.total_data_sent += raw.length
@peer.last_write_time = Networking.milliseconds
end
end
end

View File

@@ -11,14 +11,17 @@ module CyberarmEngine
type = packet.message.unpack1("C")
puts "#{host.class} received #{type_to_name(type: type)}"
pp raw
case type
when Protocol::PACKET_CONTROL
handle_control_packet(host, packet, peer)
when Protocol::PACKET_RAW
handle_raw_packet(packet)
when Protocol::PACKET_RELIABLE
handle_reliable_packet(host, packet, peer)
else
raise NotImplementedError, "A Packet handler for #{type} is not implmented!"
raise NotImplementedError, "A Packet handler for #{type_to_name(type: type)}[#{type}] is not implemented!"
end
end
@@ -56,7 +59,16 @@ module CyberarmEngine
when Protocol::CONTROL_HEARTBEAT
when Protocol::CONTROL_PING
peer.write_queue << PacketHandler.create_control_packet(
peer: peer, control_type: Protocol::CONTROL_PONG,
reliable: true,
message: [Networking.milliseconds].pack("Q") # Uint64, native endian
)
when Protocol::CONTROL_PONG
sent_time = pkt.message.unpack1("Q")
difference = Networking.milliseconds - sent_time
peer.ping = difference
end
nil
@@ -66,12 +78,30 @@ module CyberarmEngine
RawPacket.decode(packet.message)
end
def self.handle_reliable_packet(host, packet, peer)
# TODO: Preserve delivery order of reliable packets
pkt = ReliablePacket.decode(packet.message)
peer.write_queue << create_control_packet(
peer: peer,
control_type: Protocol::CONTROL_ACKNOWLEDGE,
message: [pkt.sequence_number].pack("n")
)
handle(host: host, raw: pkt.message, peer: peer)
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)
packet = Packet.new(
protocol_version: Protocol::PROTOCOL_VERSION,
peer_id: peer.id,
channel: channel,
message: ControlPacket.new(control_type: control_type, message: message).encode
)
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)

View File

@@ -1,7 +1,7 @@
module CyberarmEngine
module Networking
class ReliablePacket
attr_reader :message, :type, :control_type
attr_reader :message, :type, :sequence_number
HEADER_PACKER = "Cn"
HEADER_LENGTH = 1 + 2 # bytes
@@ -10,7 +10,7 @@ module CyberarmEngine
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)
ReliablePacket.new(type: header[0], sequence_number: header[1], message: message)
end
def initialize(sequence_number:, message:, type: Protocol::PACKET_RELIABLE)
@@ -22,7 +22,7 @@ module CyberarmEngine
def encode
header = [
@type,
@control_type
@sequence_number
].pack(HEADER_PACKER)
"#{header}#{@message}"

View File

@@ -4,7 +4,8 @@ module CyberarmEngine
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
:last_read_time, :last_write_time,
:ping
def initialize(id:, hostname:, port:)
@id = id
@@ -22,6 +23,10 @@ module CyberarmEngine
@total_packets_received = 0
@total_data_sent = 0
@total_data_received = 0
@ping = 0
@reliable_sequence_number = 65_500
end
def id=(n)
@@ -29,6 +34,10 @@ module CyberarmEngine
@id = n
end
def next_reliable_sequence_number
@reliable_sequence_number = (@reliable_sequence_number + 1) % 65_535
end
end
end
end

View File

@@ -70,7 +70,7 @@ module CyberarmEngine
# Send packet to specified peer
def send_packet(peer:, message:, reliable: false, channel: 0)
if (peer = @peers.get(peer))
if (peer = @peers[peer])
packet = PacketHandler.create_raw_packet(message, reliable, channel)
peer.write_queue << packet
else
@@ -80,12 +80,12 @@ module CyberarmEngine
# Send packet to all connected peer
def broadcast_packet(message:, reliable: false, channel: 0)
@peers.each { |peer| send_packet(peer.id, message, reliable, channel) }
@peers.each { |peer| send_packet(peer: peer.id, message: message, reliable: reliable, channel: channel) }
end
# Disconnect peer
def disconnect_client(peer:, reason: "")
if (peer = @peers.get(peer))
if (peer = @peers[peer])
packet = PacketHandler.create_disconnect_packet(peer.id, reason)
peer.write_now!(packet)
@peers.delete(peer)
@@ -115,6 +115,16 @@ module CyberarmEngine
next
end
if Networking.milliseconds - peer.last_write_time > Protocol::HEARTBEAT_INTERVAL
write(
peer: peer,
packet: PacketHandler.create_control_packet(
peer: peer,
control_type: Protocol::CONTROL_PING
)
)
end
while(packet = peer.write_queue.shift)
write(peer: peer, packet: packet)
end

View File

@@ -12,7 +12,7 @@ class IMICFPS
push_state(IMICFPS::MapEditorTool::MainMenu)
end
link "Back" do
link I18n.t("menus.back") do
pop_state
end
end

View File

@@ -9,11 +9,11 @@ class IMICFPS
pop_state
end
link "Settings" do
link I18n.t("menus.settings") do
push_state(SettingsMenu)
end
link "Leave" do
link I18n.t("menus.leave") do
push_state(MainMenu)
end
end

View File

@@ -12,7 +12,7 @@ class IMICFPS
end
end
link "Back" do
link I18n.t("menus.back") do
pop_state
end
end

View File

@@ -3,23 +3,23 @@ class IMICFPS
def setup
title IMICFPS::NAME
link "Single Player" do
link I18n.t("menus.singleplayer") do
push_state(LevelSelectMenu)
end
link "Multiplayer" do
link I18n.t("menus.multiplayer") do
push_state(MultiplayerMenu)
end
link "Settings" do
link I18n.t("menus.settings") do
push_state(SettingsMenu)
end
link "Extras" do
link I18n.t("menus.extras") do
push_state(ExtrasMenu)
end
link "Quit" do
link I18n.t("menus.quit") do
window.close
end

View File

@@ -11,7 +11,7 @@ class IMICFPS
link "Profile" do
push_state(MultiplayerProfileMenu)
end
link "Back" do
link I18n.t("menus.back") do
pop_state
end
end

View File

@@ -7,7 +7,7 @@ class IMICFPS
stack(width: 0.25, height: 1.0) do
button "Edit Profile", width: 1.0
button "Log Out", width: 1.0
button "Back", width: 1.0, margin_top: 64 do
button I18n.t("menus.back"), width: 1.0, margin_top: 64 do
pop_state
end
end

View File

@@ -34,7 +34,7 @@ class IMICFPS
button "Host Game", width: 1.0
button "Direct Connect", width: 1.0
button "Back", width: 1.0, margin_top: 64 do
button I18n.t("menus.back"), width: 1.0, margin_top: 64 do
pop_state
end
end

View File

@@ -29,7 +29,7 @@ class IMICFPS
end
end
button "Back", width: 1.0, margin_top: 64 do
button I18n.t("menus.back"), width: 1.0, margin_top: 64 do
pop_state
end
end

View File

@@ -12,6 +12,11 @@ class IMICFPS
super(width: window_width, height: window_height, fullscreen: fullscreen, resizable: true, update_interval: 1000.0/fps_target)
end
$window = self
I18n.load_path << Dir["#{GAME_ROOT_PATH}/locales/*.yml"]
I18n.default_locale = :en
language = Gosu.language.split("_").first.to_sym
I18n.locale = language if I18n.available_locales.include?(language)
@needs_cursor = false
@cursor = Gosu::Image.new(IMICFPS::GAME_ROOT_PATH + "/static/cursors/pointer.png")
@number_of_vertices = 0