mirror of
https://github.com/cyberarm/i-mic-rts.git
synced 2025-12-13 06:52:33 +00:00
Basic networking implemented, currently non functional
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
data/settings.json
|
||||
data/settings.json
|
||||
doc/
|
||||
.yardoc/
|
||||
@@ -9,6 +9,7 @@ end
|
||||
require "nokogiri"
|
||||
|
||||
require "json"
|
||||
require "socket"
|
||||
|
||||
require_relative "lib/version"
|
||||
require_relative "lib/errors"
|
||||
@@ -39,6 +40,12 @@ require_relative "lib/director"
|
||||
require_relative "lib/player"
|
||||
require_relative "lib/connection"
|
||||
|
||||
require_relative "lib/networking/protocol"
|
||||
require_relative "lib/networking/packet"
|
||||
require_relative "lib/networking/server"
|
||||
require_relative "lib/networking/client"
|
||||
require_relative "lib/networking/connection"
|
||||
|
||||
IMICRTS::Setting.setup
|
||||
|
||||
IMICRTS::Window.new(width: Gosu.screen_width / 4 * 3, height: Gosu.screen_height / 4 * 3, fullscreen: false, resizable: true).show
|
||||
@@ -1,7 +1,30 @@
|
||||
class IMICRTS
|
||||
# {IMICRTS::Connection} is the abstract middleman that Director sends/receives Orders from.
|
||||
# not to be confused with {IMICRTS::Networking::Connection}
|
||||
class Connection
|
||||
def initialize(*args)
|
||||
# Connection modes:
|
||||
# :virtual => emulates networking without sockets (used for solo play)
|
||||
# :host => starts server
|
||||
# :client => connects to server/host
|
||||
def initialize(director:, mode:, hostname: "localhost", port: 56789)
|
||||
@director = director
|
||||
@mode = mode
|
||||
@hostname = hostname
|
||||
@port = port
|
||||
|
||||
@pending_orders = []
|
||||
|
||||
case @mode
|
||||
when :virtual
|
||||
when :host
|
||||
@server = Networking::Server.new(director: @director, hostname: @hostname, port: @port)
|
||||
|
||||
@connection = Networking::Connection.new(director: @director, hostname: @hostname, port: @port)
|
||||
when :client
|
||||
@connection = Networking::Connection.new(director: @director, hostname: @hostname, port: @port)
|
||||
else
|
||||
raise RuntimeError, "Unable to process Connection of type: #{@mode.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def add_order(order)
|
||||
@@ -9,15 +32,37 @@ class IMICRTS
|
||||
end
|
||||
|
||||
def update
|
||||
data = @pending_orders.sort_by { |order| order.tick_id }.map do |order|
|
||||
# data = @pending_orders.sort_by { |order| order.tick_id }.map do |order|
|
||||
|
||||
# Order serialized size in bytes + serialized order data
|
||||
[order.serialized_order.length].pack("n") + order.serialized_order
|
||||
end.join
|
||||
# # Order serialized size in bytes + serialized order data
|
||||
# pp order.serialized_order
|
||||
# [order.serialized_order.length].pack("n") + order.serialized_order
|
||||
# end.join
|
||||
|
||||
# p data if data.length > 0
|
||||
if @mode == :virtual
|
||||
else
|
||||
@pending_orders.each do |order|
|
||||
# TODO: make this include order_id and tick_id
|
||||
@server.broadcast(order.serialized_order) if @server
|
||||
@connection.write(order.serialized_order) unless @server
|
||||
|
||||
@pending_orders.clear
|
||||
@pending_orders.delete(order)
|
||||
end
|
||||
end
|
||||
|
||||
case @mode
|
||||
when :virtual
|
||||
when :host
|
||||
@server.update
|
||||
@connection.update
|
||||
when :client
|
||||
@connection.update
|
||||
end
|
||||
end
|
||||
|
||||
def finalize
|
||||
@server.stop if @server
|
||||
@connection.close if @connection
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,7 @@
|
||||
class IMICRTS
|
||||
class Director
|
||||
attr_reader :current_tick, :map
|
||||
def initialize(map:, players:, networking_mode: :virtual, tick_rate: 10)
|
||||
def initialize(map:, players:, networking_mode:, tick_rate: 10)
|
||||
@map = map
|
||||
@players = players
|
||||
@connection = IMICRTS::Connection.new(director: self, mode: networking_mode)
|
||||
@@ -104,5 +104,9 @@ class IMICRTS
|
||||
def entities
|
||||
@players.map { |player| player.entities }.flatten
|
||||
end
|
||||
|
||||
def finalize
|
||||
@connection.finalize
|
||||
end
|
||||
end
|
||||
end
|
||||
52
lib/networking/client.rb
Normal file
52
lib/networking/client.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
class IMICRTS
|
||||
class Networking
|
||||
class Client
|
||||
attr_reader :packets_sent, :packets_received,
|
||||
:data_sent, :data_received
|
||||
def initialize(socket)
|
||||
@socket = socket
|
||||
|
||||
@packets_sent, @packets_received = 0, 0
|
||||
@data_sent, @data_received = 0, 0
|
||||
|
||||
@read_queue = []
|
||||
@write_queue = []
|
||||
end
|
||||
|
||||
def update
|
||||
if connected?
|
||||
buffer = @socket.recv_nonblock(Networking::Protocol.max_packet_length, exception: false)
|
||||
|
||||
if buffer.is_a?(String)
|
||||
order = buffer.split(Protocol::END_OF_MESSAGE).first.strip
|
||||
end
|
||||
|
||||
until(@write_queue.size == 0)
|
||||
packet = @write_queue.shift
|
||||
|
||||
@socket.write_nonblock(packet + Protocol::END_OF_MESSAGE, exception: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def connected?
|
||||
!@socket.closed?
|
||||
end
|
||||
|
||||
def close(packet = nil)
|
||||
@socket.write(Networking::Packet.pack(packet) + Protocol::END_OF_MESSAGE) if packet
|
||||
|
||||
@socket.close
|
||||
end
|
||||
|
||||
def write(data)
|
||||
packet = Networking::Packet.new(type: Protocol::RELIABLE, client_id: 0, data: data)
|
||||
@write_queue << Networking::Packet.pack(packet)
|
||||
end
|
||||
|
||||
def read
|
||||
return @read_queue.shift
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
25
lib/networking/connection.rb
Normal file
25
lib/networking/connection.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class IMICRTS
|
||||
class Networking
|
||||
class Connection
|
||||
def initialize(director:, hostname:, port:)
|
||||
@director = director
|
||||
@hostname = hostname
|
||||
@port = port
|
||||
|
||||
@client = Networking::Client.new(TCPSocket.new(@hostname, @port))
|
||||
end
|
||||
|
||||
def connected?
|
||||
@client.connected?
|
||||
end
|
||||
|
||||
def update
|
||||
@client.update
|
||||
end
|
||||
|
||||
def close
|
||||
@client.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
42
lib/networking/packet.rb
Normal file
42
lib/networking/packet.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
class IMICRTS
|
||||
class Networking
|
||||
class Packet
|
||||
# Packet
|
||||
# [
|
||||
# header_packet_type,
|
||||
# header_packet_length,
|
||||
# header_packet_sequence_id,
|
||||
# header_packet_client_id,
|
||||
#
|
||||
# data
|
||||
# ]
|
||||
attr_reader :type, :sequence_id, :client_id, :data
|
||||
def initialize(type:, sequence_id: nil, client_id:, data:)
|
||||
@type = type
|
||||
@sequence_id = sequence_id
|
||||
@client_id = client_id
|
||||
@data = data
|
||||
end
|
||||
|
||||
def self.pack(packet)
|
||||
header = nil
|
||||
|
||||
# Packet Type: Char => "C"
|
||||
# Packet Sequence ID: 32-bit unsigned Integer => "N"
|
||||
# Packet Client ID: 16-bit unsigned Integer => "n"
|
||||
|
||||
if packet.sequence_id
|
||||
header = [packet.type, packet.sequence_id, packet.client_id].pack("CNn")
|
||||
else
|
||||
header = [packet.type, packet.client_id].pack("Cn")
|
||||
end
|
||||
|
||||
header += packet.data
|
||||
end
|
||||
|
||||
def self.unpack(raw_string)
|
||||
pp raw_string.unpack("Cn")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
lib/networking/protocol.rb
Normal file
32
lib/networking/protocol.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
class IMICRTS
|
||||
class Networking
|
||||
module Protocol
|
||||
VERSION = 0x01
|
||||
END_OF_MESSAGE = "\r\r\r\n"
|
||||
|
||||
ACKNOWLEDGE = 0x01
|
||||
CONNECT = 0x02
|
||||
VERIFY_CONNECT = 0x03
|
||||
DISCONNECT = 0x04
|
||||
PING = 0x05
|
||||
|
||||
RELIABLE = 0x20
|
||||
UNRELIABLE = 0x21
|
||||
UNSEQUENCED = 0x22
|
||||
FRAGMENT = 0x23
|
||||
UNRELIABLE_FRAGMENT = 0x24
|
||||
|
||||
BANDWIDTH_LIMIT = 0x40
|
||||
THROTTLE_CONFIGURE = 0x41
|
||||
COUNT = 0x42
|
||||
MASK = 0x43
|
||||
|
||||
HEADER_FLAG_COMPRESSED = 0x00
|
||||
HEADER_FLAG_SENT_TIME = 0x01
|
||||
|
||||
def self.max_packet_length
|
||||
1024 # => 1 Kb
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
lib/networking/server.rb
Normal file
51
lib/networking/server.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
class IMICRTS
|
||||
class Networking
|
||||
class Server
|
||||
def initialize(director:, hostname:, port:, max_peers: 8)
|
||||
@director = director
|
||||
@hostname, @port = hostname, port
|
||||
@max_peers = max_peers
|
||||
|
||||
@socket = TCPServer.new(hostname, port)
|
||||
@clients = []
|
||||
end
|
||||
|
||||
def update
|
||||
new_client = @socket.accept_nonblock(exception: false)
|
||||
|
||||
if new_client != :wait_readable
|
||||
handle_client(new_client)
|
||||
end
|
||||
|
||||
@clients.each do |client|
|
||||
client.update
|
||||
end
|
||||
end
|
||||
|
||||
def handle_client(client)
|
||||
if @clients.size < @max_peers
|
||||
@clients << Networking::Client.new(client)
|
||||
else
|
||||
client.write("\u00000128")
|
||||
client.close
|
||||
end
|
||||
end
|
||||
|
||||
def broadcast(packet)
|
||||
@clients.each do |client|
|
||||
client.write(packet)
|
||||
end
|
||||
end
|
||||
|
||||
def write(client_id, packet)
|
||||
client = @clients.find { |cl| cl.uid == client_id }
|
||||
|
||||
client.write(packet) if client
|
||||
end
|
||||
|
||||
def stop
|
||||
@socket.close if @socket
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,7 +33,7 @@ class IMICRTS
|
||||
end
|
||||
|
||||
class ScheduledOrder
|
||||
attr_reader :tick_id, :serialized_order
|
||||
attr_reader :order_id, :tick_id, :serialized_order
|
||||
def initialize(order_id, tick_id, serialized_order)
|
||||
@order_id = order_id
|
||||
@tick_id, @serialized_order = tick_id, serialized_order
|
||||
|
||||
@@ -3,9 +3,10 @@ class IMICRTS
|
||||
Overlay = Struct.new(:image, :position, :angle, :alpha)
|
||||
def setup
|
||||
window.show_cursor = true
|
||||
@options[:networking_mode] ||= :host
|
||||
|
||||
@player = Player.new(id: 0)
|
||||
@director = Director.new(map: Map.new(map_file: "maps/test_map.tmx"), players: [@player])
|
||||
@director = Director.new(map: Map.new(map_file: "maps/test_map.tmx"), networking_mode: @options[:networking_mode], players: [@player])
|
||||
|
||||
@selected_entities = []
|
||||
@overlays = []
|
||||
@@ -178,7 +179,7 @@ class IMICRTS
|
||||
end
|
||||
|
||||
def finalize
|
||||
# TODO: Release bound objects/remove self from Window.states array
|
||||
@director.finalize
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,7 +18,7 @@ class IMICRTS
|
||||
stack do
|
||||
case elements[index]
|
||||
when :edit_line
|
||||
edit_line Setting.get(:player_name)
|
||||
@player_name = edit_line Setting.get(:player_name)
|
||||
when :button
|
||||
button item
|
||||
when :toggle_button
|
||||
@@ -35,7 +35,8 @@ class IMICRTS
|
||||
|
||||
flow(width: 1.0) do
|
||||
button("Accept", width: 0.5) do
|
||||
push_state(Game)
|
||||
Setting.set(:player_name, @player_name.value)
|
||||
push_state(Game, networking_mode: :virtual)
|
||||
end
|
||||
|
||||
button("Back", align: :right) do
|
||||
|
||||
@@ -44,5 +44,12 @@ class IMICRTS
|
||||
def dt
|
||||
delta_time / 1000.0
|
||||
end
|
||||
|
||||
# Override CyberarmEngine::Window#push_state to only ever have 1 state
|
||||
def push_state(*args)
|
||||
@states.clear
|
||||
|
||||
super(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user