mirror of
https://github.com/cyberarm/i-mic-rts.git
synced 2025-12-15 15:52:34 +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 "nokogiri"
|
||||||
|
|
||||||
require "json"
|
require "json"
|
||||||
|
require "socket"
|
||||||
|
|
||||||
require_relative "lib/version"
|
require_relative "lib/version"
|
||||||
require_relative "lib/errors"
|
require_relative "lib/errors"
|
||||||
@@ -39,6 +40,12 @@ require_relative "lib/director"
|
|||||||
require_relative "lib/player"
|
require_relative "lib/player"
|
||||||
require_relative "lib/connection"
|
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::Setting.setup
|
||||||
|
|
||||||
IMICRTS::Window.new(width: Gosu.screen_width / 4 * 3, height: Gosu.screen_height / 4 * 3, fullscreen: false, resizable: true).show
|
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
|
class IMICRTS
|
||||||
|
# {IMICRTS::Connection} is the abstract middleman that Director sends/receives Orders from.
|
||||||
|
# not to be confused with {IMICRTS::Networking::Connection}
|
||||||
class 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 = []
|
@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
|
end
|
||||||
|
|
||||||
def add_order(order)
|
def add_order(order)
|
||||||
@@ -9,15 +32,37 @@ class IMICRTS
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
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 size in bytes + serialized order data
|
||||||
[order.serialized_order.length].pack("n") + order.serialized_order
|
# pp order.serialized_order
|
||||||
end.join
|
# [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
|
end
|
||||||
end
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
class IMICRTS
|
class IMICRTS
|
||||||
class Director
|
class Director
|
||||||
attr_reader :current_tick, :map
|
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
|
@map = map
|
||||||
@players = players
|
@players = players
|
||||||
@connection = IMICRTS::Connection.new(director: self, mode: networking_mode)
|
@connection = IMICRTS::Connection.new(director: self, mode: networking_mode)
|
||||||
@@ -104,5 +104,9 @@ class IMICRTS
|
|||||||
def entities
|
def entities
|
||||||
@players.map { |player| player.entities }.flatten
|
@players.map { |player| player.entities }.flatten
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def finalize
|
||||||
|
@connection.finalize
|
||||||
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
class ScheduledOrder
|
class ScheduledOrder
|
||||||
attr_reader :tick_id, :serialized_order
|
attr_reader :order_id, :tick_id, :serialized_order
|
||||||
def initialize(order_id, tick_id, serialized_order)
|
def initialize(order_id, tick_id, serialized_order)
|
||||||
@order_id = order_id
|
@order_id = order_id
|
||||||
@tick_id, @serialized_order = tick_id, serialized_order
|
@tick_id, @serialized_order = tick_id, serialized_order
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ class IMICRTS
|
|||||||
Overlay = Struct.new(:image, :position, :angle, :alpha)
|
Overlay = Struct.new(:image, :position, :angle, :alpha)
|
||||||
def setup
|
def setup
|
||||||
window.show_cursor = true
|
window.show_cursor = true
|
||||||
|
@options[:networking_mode] ||= :host
|
||||||
|
|
||||||
@player = Player.new(id: 0)
|
@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 = []
|
@selected_entities = []
|
||||||
@overlays = []
|
@overlays = []
|
||||||
@@ -178,7 +179,7 @@ class IMICRTS
|
|||||||
end
|
end
|
||||||
|
|
||||||
def finalize
|
def finalize
|
||||||
# TODO: Release bound objects/remove self from Window.states array
|
@director.finalize
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -18,7 +18,7 @@ class IMICRTS
|
|||||||
stack do
|
stack do
|
||||||
case elements[index]
|
case elements[index]
|
||||||
when :edit_line
|
when :edit_line
|
||||||
edit_line Setting.get(:player_name)
|
@player_name = edit_line Setting.get(:player_name)
|
||||||
when :button
|
when :button
|
||||||
button item
|
button item
|
||||||
when :toggle_button
|
when :toggle_button
|
||||||
@@ -35,7 +35,8 @@ class IMICRTS
|
|||||||
|
|
||||||
flow(width: 1.0) do
|
flow(width: 1.0) do
|
||||||
button("Accept", width: 0.5) do
|
button("Accept", width: 0.5) do
|
||||||
push_state(Game)
|
Setting.set(:player_name, @player_name.value)
|
||||||
|
push_state(Game, networking_mode: :virtual)
|
||||||
end
|
end
|
||||||
|
|
||||||
button("Back", align: :right) do
|
button("Back", align: :right) do
|
||||||
|
|||||||
@@ -44,5 +44,12 @@ class IMICRTS
|
|||||||
def dt
|
def dt
|
||||||
delta_time / 1000.0
|
delta_time / 1000.0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Override CyberarmEngine::Window#push_state to only ever have 1 state
|
||||||
|
def push_state(*args)
|
||||||
|
@states.clear
|
||||||
|
|
||||||
|
super(*args)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user