Cleanup, moved Map lifecycle into Director, added renderer_info command

This commit is contained in:
2020-12-02 11:38:10 -06:00
parent c7590366a6
commit 9aa5dc7174
15 changed files with 188 additions and 252 deletions

View File

@@ -7,10 +7,11 @@ class IMICFPS
def handle(subscriber, context, *args) def handle(subscriber, context, *args)
return unless subscriber.entity == args.first.first return unless subscriber.entity == args.first.first
event = EventHandler::Event.new(entity: subscriber.entity, context: context) event = EventHandler::Event.new(entity: subscriber.entity, context: context)
subscriber.trigger(event) subscriber.trigger(event)
end end
end end
end end
end end

View File

@@ -60,9 +60,9 @@ class IMICFPS
end end
def jump def jump
if InputMapper.down?(:jump) && window.current_state.map.collision_manager.on_ground?(self) return unless InputMapper.down?(:jump) && window.director.map.collision_manager.on_ground?(self)
@velocity.y = 1.5
end @velocity.y = 1.5
end end
end end
end end

View File

@@ -7,25 +7,24 @@ class IMICFPS
end end
def insert_entity(package, name, position, orientation, data = {}) def insert_entity(package, name, position, orientation, data = {})
ent = MapParser::Entity.new(package, name, position, orientation, Vector.new(1,1,1)) ent = MapParser::Entity.new(package, name, position, orientation, Vector.new(1, 1, 1))
add_entity(IMICFPS::Entity.new(map_entity: ent, manifest: Manifest.new(package: package, name: name))) add_entity(IMICFPS::Entity.new(map_entity: ent, manifest: Manifest.new(package: package, name: name)))
end end
def find_entity(entity) def find_entity(entity)
@entities.detect {|entity| entity == entity} @entities.detect { |e| e == entity }
end end
def find_entity_by(name:) def find_entity_by(name:)
@entities.detect { |entity| entity.name == name} @entities.detect { |entity| entity.name == name }
end end
def remove_entity(entity) def remove_entity(entity)
ent = @entities.detect {|entity| entity == entity} return unless (ent = @entities.detect { |e| e == entity })
if ent
@collision_manager.remove(entity) if @collision_manager && entity.manifest.collision @collision_manager.remove(entity) if @collision_manager && entity.manifest.collision
@publisher.publish(:destroy, nil, entity) @publisher.publish(:destroy, nil, entity)
@entities.delete(ent) @entities.delete(ent)
end
end end
def entities def entities

View File

@@ -4,8 +4,8 @@ class IMICFPS
include LightManager include LightManager
include CommonMethods include CommonMethods
attr_reader :collision_manager attr_reader :collision_manager, :gravity
attr_reader :gravity
def initialize(map_parser:, gravity: IMICFPS::EARTH_GRAVITY) def initialize(map_parser:, gravity: IMICFPS::EARTH_GRAVITY)
@map_parser = map_parser @map_parser = map_parser
@gravity = gravity @gravity = gravity
@@ -18,24 +18,92 @@ class IMICFPS
end end
def setup def setup
add_entity(Terrain.new(map_entity: @map_parser.terrain, manifest: Manifest.new(package: @map_parser.terrain.package, name: @map_parser.terrain.name))) add_terrain
add_skybox
add_lights
add_entities
add_entity(Skydome.new(map_entity: @map_parser.skydome, manifest: Manifest.new(package: @map_parser.skydome.package, name: @map_parser.skydome.name), backface_culling: false)) # TODO: Add player entity from director
add_entity(
Player.new(
spawnpoint: @map_parser.spawnpoints.sample,
manifest: Manifest.new(
package: "base",
name: "character"
)
)
)
end
def add_terrain
add_entity(
Terrain.new(
map_entity: @map_parser.terrain,
manifest: Manifest.new(
package: @map_parser.terrain.package,
name: @map_parser.terrain.name
)
)
)
end
def add_skybox
add_entity(
Skydome.new(
map_entity: @map_parser.skydome,
backface_culling: false,
manifest: Manifest.new(
package: @map_parser.skydome.package,
name: @map_parser.skydome.name
)
)
)
end
def add_lights
@map_parser.lights.each do |l| @map_parser.lights.each do |l|
add_light(Light.new(id: available_light, type: l.type, position: l.position, diffuse: l.diffuse, ambient: l.ambient, specular: l.specular, intensity: l.intensity)) add_light(
Light.new(
id: available_light,
type: l.type,
position: l.position,
diffuse: l.diffuse,
ambient: l.ambient,
specular: l.specular,
intensity: l.intensity
)
)
end end
@map_parser.entities.each do |ent|
add_entity(Entity.new(map_entity: ent, manifest: Manifest.new(package: ent.package, name: ent.name)))
end
add_entity(Player.new(spawnpoint: @map_parser.spawnpoints.sample, manifest: Manifest.new(package: "base", name: "character")))
# Default lights if non are defined # Default lights if non are defined
if @map_parser.lights.size == 0 return unless @map_parser.lights.size.zero?
add_light(Light.new(id: available_light, position: Vector.new(30, 10.0, 30)))
add_light(Light.new(id: available_light, position: Vector.new(0, 100, 0), diffuse: Color.new(1.0, 0.5, 0.1))) add_light(
Light.new(
id: available_light,
position: Vector.new(30, 10.0, 30)
)
)
add_light(
Light.new(
id: available_light,
position: Vector.new(0, 100, 0), diffuse: Color.new(1.0, 0.5, 0.1)
)
)
end
def add_entities
@map_parser.entities.each do |ent|
add_entity(
Entity.new(
map_entity: ent,
manifest: Manifest.new(
package: ent.package,
name: ent.name
)
)
)
end end
end end
@@ -48,7 +116,7 @@ class IMICFPS
Gosu.gl do Gosu.gl do
gl_error? gl_error?
glClearColor(0,0.2,0.5,1) # skyish blue glClearColor(0, 0.2, 0.5, 1) # skyish blue
gl_error? gl_error?
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # clear the screen and the depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # clear the screen and the depth buffer
gl_error? gl_error?

View File

@@ -2,6 +2,7 @@ module CyberarmEngine
module Networking module Networking
class Connection class Connection
attr_reader :hostname, :port, :peer attr_reader :hostname, :port, :peer
def initialize(hostname:, port:, channels: 3) def initialize(hostname:, port:, channels: 3)
@hostname = hostname @hostname = hostname
@port = port @port = port
@@ -26,7 +27,7 @@ module CyberarmEngine
# Functions # # Functions #
def send_packet(message:, reliable: false, channel: 0) def send_packet(message:, reliable: false, channel: 0)
@peer.write_queue << PacketHandler.create_raw_packet(peer: @peer, message: message, reliable: reliable, channel: channel) @peer.write_queue << PacketHandler.create_raw_packet(peer: @peer, message: message, reliable: reliable, channel: channel)
end end
def connect(timeout: Protocol::TIMEOUT_PERIOD) def connect(timeout: Protocol::TIMEOUT_PERIOD)

View File

@@ -32,9 +32,9 @@ module CyberarmEngine
when Protocol::CONTROL_CONNECT # TOSERVER only when Protocol::CONTROL_CONNECT # TOSERVER only
if (peer_id = host.available_peer_id) if (peer_id = host.available_peer_id)
peer.id = peer_id peer.id = peer_id
host.clients << peer host.peers << peer
peer.write_queue << create_control_packet(peer: peer, control_type: Protocol::CONTROL_SET_PEER_ID, message: [peer_id].pack("n")) 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) host.peer_connected(peer: peer)
else else
host.write( host.write(
peer: peer, peer: peer,
@@ -52,7 +52,7 @@ module CyberarmEngine
when Protocol::CONTROL_DISCONNECT when Protocol::CONTROL_DISCONNECT
if host.is_a?(Server) if host.is_a?(Server)
host.client_disconnected(peer: peer) host.peer_disconnected(peer: peer)
else else
host.disconnected(reason: pkt.message) host.disconnected(reason: pkt.message)
end end

View File

@@ -1,7 +1,7 @@
module CyberarmEngine module CyberarmEngine
module Networking module Networking
class Server class Server
attr_reader :hostname, :port, :max_clients attr_reader :hostname, :port, :max_peers, :peers
attr_accessor :total_packets_sent, :total_packets_received, attr_accessor :total_packets_sent, :total_packets_received,
:total_data_sent, :total_data_received, :total_data_sent, :total_data_received,
:last_read_time, :last_write_time :last_read_time, :last_write_time
@@ -9,12 +9,12 @@ module CyberarmEngine
def initialize( def initialize(
hostname: CyberarmEngine::Networking::DEFAULT_SERVER_HOSTNAME, hostname: CyberarmEngine::Networking::DEFAULT_SERVER_HOSTNAME,
port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT, port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT,
max_clients: CyberarmEngine::Networking::DEFAULT_PEER_LIMIT, max_peers: CyberarmEngine::Networking::DEFAULT_PEER_LIMIT,
channels: 3 channels: 3
) )
@hostname = hostname @hostname = hostname
@port = port @port = port
@max_clients = max_clients + 2 @max_peers = max_peers + 2
@channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) } @channels = Array(0..channels).map { |id| Channel.new(id: id, mode: :default) }
@peers = [] @peers = []
@@ -28,35 +28,26 @@ module CyberarmEngine
@total_data_received = 0 @total_data_received = 0
end end
# Helpers #
def connected_clients
@peers.size
end
def clients
@peers
end
# Callbacks # # Callbacks #
# Called when client connects # Called when peer connects
def client_connected(peer:) def peer_connected(peer:)
end end
# Called when client times out or explicitly disconnects # Called when peer times out or explicitly disconnects
def client_disconnected(peer:, reason:) def peer_disconnected(peer:, reason:)
end end
### REMOVE? ### ### REMOVE? ###
# Called when client was not sending heartbeats or regular packets for a # Called when peer was not sending heartbeats or regular packets for a
# period of time, but was not logically disconnected and removed, and started # period of time, but was not logically disconnected and removed, and started
# send packets again. # send packets again.
# #
# TLDR: Client was temporarily unreachable but did not timeout. # TLDR: peer was temporarily unreachable but did not timeout.
def client_reconnected(peer:) def peer_reconnected(peer:)
end end
# Called when a (logical) packet is received from client # Called when a (logical) packet is received from peer
def packet_received(peer:, message:, channel:) def packet_received(peer:, message:, channel:)
end end
@@ -84,7 +75,7 @@ module CyberarmEngine
end end
# Disconnect peer # Disconnect peer
def disconnect_client(peer:, reason: "") def disconnect_peer(peer:, reason: "")
if (peer = @peers[peer]) if (peer = @peers[peer])
packet = PacketHandler.create_disconnect_packet(peer.id, reason) packet = PacketHandler.create_disconnect_packet(peer.id, reason)
peer.write_now!(packet) peer.write_now!(packet)
@@ -110,7 +101,7 @@ module CyberarmEngine
message: message message: message
) )
) )
client_disconnected(peer: peer, reason: message) peer_disconnected(peer: peer, reason: message)
@peers.delete(peer) @peers.delete(peer)
next next
end end
@@ -135,7 +126,7 @@ module CyberarmEngine
def available_peer_id def available_peer_id
peer_ids = @peers.map(&:id) peer_ids = @peers.map(&:id)
ids = (2..@max_clients).to_a - peer_ids ids = (2..@max_peers).to_a - peer_ids
ids.size.positive? ? ids.first : nil ids.size.positive? ? ids.first : nil
end end
@@ -157,7 +148,7 @@ module CyberarmEngine
packet: PacketHandler.create_control_packet( packet: PacketHandler.create_control_packet(
peer: peer, peer: peer,
control_type: Protocol::CONTROL_DISCONNECT, control_type: Protocol::CONTROL_DISCONNECT,
message: "ERROR: client not connected" message: "ERROR: peer not connected"
) )
) )
end end

View File

@@ -1,29 +1,36 @@
class IMICFPS class IMICFPS
module Networking module Networking
class Director class Director
attr_reader :address, :port, :tick_rate, :storage, :map attr_reader :tick_rate, :storage, :map, :server, :connection
def initialize(address: DEFAULT_SERVER_HOST, port: DEFAULT_SERVER_PORT, tick_rate: 2)
@address = address def initialize(tick_rate: 15)
@port = port
@tick_rate = (1000.0 / tick_rate) / 1000.0 @tick_rate = (1000.0 / tick_rate) / 1000.0
@server = Server.new(address: @address, port: @port, max_peers: DEFAULT_PEER_LIMIT) @last_tick_time = CyberarmEngine::Networking.milliseconds
@server.bind
@last_tick_time = Networking.milliseconds
@directing = true @directing = true
@storage = {} @storage = {}
@map = nil @map = nil
end end
def host_server(hostname: CyberarmEngine::Networking::DEFAULT_SERVER_HOSTNAME, port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT, max_peers: CyberarmEngine::Networking::DEFAULT_PEER_LIMIT)
@server = Server.new(hostname: hostname, port: port, max_peers: max_peers)
@server.bind
end
def connect(hostname:, port: CyberarmEngine::Networking::DEFAULT_SERVER_PORT)
@connection = Connection.new(hostname: hostname, port: port)
@connection.connect
end
def load_map(map_parser:) def load_map(map_parser:)
# TODO: send map_change to clients # TODO: send map_change to clients
@map = Map.new(map_parser: map_parser) @map = Map.new(map_parser: map_parser)
@map.setup
end end
def run def run
Thread.start do |thread| Thread.start do
while(@directing) while @directing
dt = milliseconds - @last_tick_time dt = milliseconds - @last_tick_time
tick(dt) tick(dt)
@@ -35,18 +42,20 @@ class IMICFPS
end end
def tick(delta_time) def tick(delta_time)
if @map return unless @map
Publisher.instance.publish(:tick, delta_time * 1000.0)
@map.update Publisher.instance.publish(:tick, delta_time * 1000.0)
@server.update
end @map.update
@server&.update
@connection&.update
end end
def shutdown def shutdown
@directing = false @directing = false
@server.close @server&.close
@connection&.close
end end
end end
end end
end end

View File

@@ -1,23 +0,0 @@
class IMICFPS
module Networking
class Peer
attr_reader :id, :address, :port, :packet_read_queue, :packet_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:, address:, port:)
@id = id
@address, @port = address, port
@packet_write_queue = []
@packet_read_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
end
end
end

View File

@@ -1,135 +1,14 @@
class IMICFPS class IMICFPS
module Networking module Networking
class Server class Server < CyberarmEngine::Networking::Server
attr_reader :address, :port, :max_peers, :peers def connected(peer:)
attr_accessor :total_packets_sent, :total_packets_received, :total_data_sent, :total_data_received, :last_read_time, :last_write_time
def initialize(address:, port:, max_peers:)
@address = address
@port = port
@max_peers = max_peers
@peers = Array.new(@max_peers + 1, nil)
@read_buffer = ReadBuffer.new
@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
# Peer ID 0 is reserved for unconnected peers to pass packet validation def disconnected(peer:, reason:)
def create_peer(address:, port:)
@peers.each_with_index do |peer, i|
unless peer
new_peer = Peer.new(id: i + 1, address: address, port: port)
@peers[i + 1] = new_peer
return new_peer
end
end
return nil
end end
def get_peer(peer_id) def packet_received(peer:, message:, channel:)
@peers[peer_id]
end
def remove_peer(peer_id)
@peers[peer_id] = nil
end
def bind
@socket = UDPSocket.new
@socket.bind(@address, @port)
end
def send_packet( peer_id, packet )
if peer = get_peer(peer_id)
packets = Packet.splinter(packet)
packets.each { |pkt| peer.packet_write_queue.push(pkt) }
end
end
def broadcast_packet(packet)
@peers.each do |peer|
next unless peer
send_packet(peer.id, packet)
end
end
def update
while(read)
end
@peers.each do |peer|
next unless peer
peer.packet_write_queue.each do |packet|
write(peer, packet)
peer.packet_write_queue.delete(packet)
end
end
# "deliver" packets to peers, record stats to peers
@read_buffer.reconstruct_packets.each do |packet, addr_info|
peer = nil
# initial connection
if packet.peer_id == 0 && packet.type == Protocol::CONNECT
peer = create_peer(address: addr_info[2], port: addr_info[1])
send_packet(
peer.id,
Packet.new(
peer_id: 0,
sequence: 0,
type: Protocol::VERIFY_CONNECT,
payload: [peer.id].pack("C")
)
)
else
peer = get_peer(packet.peer_id)
end
end
# broadcast_packet(Packet.new(peer_id: 0, sequence: 0, type: Protocol::HEARTBEAT, payload: [Networking.milliseconds].pack("G")))
end
def close
@socket.close if @socket
end
private
def read
data, addr = @socket.recvfrom_nonblock(Protocol::MAX_PACKET_SIZE)
@read_buffer.add(data, addr )
@total_packets_received += 1
@total_data_received += data.length
@last_read_time = Networking.milliseconds
return true
rescue IO::WaitReadable
return false
end
def write(peer, packet)
raw = packet.encode
@socket.send( raw, 0, peer.address, 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 end
end end

View File

@@ -10,7 +10,7 @@ class IMICFPS
end end
def map def map
$window.current_state.map $window.director.map
end end
end end
end end

View File

@@ -1,16 +1,13 @@
class IMICFPS class IMICFPS
class Game < GameState class Game < GameState
attr_reader :map attr_reader :map
def setup
@map = Map.new(map_parser: @options[:map_parser])
@map.setup
@player = @map.find_entity_by(name: "character") def setup
window.director.load_map(map_parser: @options[:map_parser])
@player = window.director.map.find_entity_by(name: "character")
@camera = PerspectiveCamera.new( position: @player.position.clone, aspect_ratio: window.aspect_ratio ) @camera = PerspectiveCamera.new( position: @player.position.clone, aspect_ratio: window.aspect_ratio )
@camera_controller = CameraController.new(mode: :fpv, camera: @camera, entity: @player) @camera_controller = CameraController.new(mode: :fpv, camera: @camera, entity: @player)
# @director = Networking::Director.new
# @director.load_map(map_parser: @options[:map_parser])
# @connection = Networking::Connection.new(address: "localhost", port: Networking::DEFAULT_SERVER_PORT) # @connection = Networking::Connection.new(address: "localhost", port: Networking::DEFAULT_SERVER_PORT)
# @connection.connect # @connection.connect
@@ -26,32 +23,20 @@ class IMICFPS
end end
def draw def draw
@map.render(@camera) window.director.map.render(@camera)
@hud.draw @hud.draw
end end
def update def update
update_text
control_player control_player
@hud.update @hud.update
@camera_controller.update @camera_controller.update
# @connection.update # @connection.update
# @director.tick(window.dt) window.director.tick(window.dt)
@map.update
@demo.update if @demo @demo&.update
end
def update_text
string = <<-eos
OpenGL Vendor: #{glGetString(GL_VENDOR)}
OpenGL Renderer: #{glGetString(GL_RENDERER)}
OpenGL Version: #{glGetString(GL_VERSION)}
OpenGL Shader Language Version: #{glGetString(GL_SHADING_LANGUAGE_VERSION)}
eos
end end
def control_player def control_player
@@ -73,25 +58,25 @@ eos
return return
end end
@demo.button_down(id) if @demo @demo&.button_down(id)
@camera_controller.button_down(id) @camera_controller.button_down(id)
InputMapper.keydown(id) InputMapper.keydown(id)
Publisher.instance.publish(:button_down, nil, id) Publisher.instance.publish(:button_down, nil, id)
@map.entities.each do |entity| window.director.map.entities.each do |entity|
entity.button_down(id) if defined?(entity.button_down) entity.button_down(id) if defined?(entity.button_down)
end end
end end
def button_up(id) def button_up(id)
@demo.button_up(id) if @demo @demo&.button_up(id)
@camera_controller.button_up(id) @camera_controller.button_up(id)
InputMapper.keyup(id) InputMapper.keyup(id)
Publisher.instance.publish(:button_up, nil, id) Publisher.instance.publish(:button_up, nil, id)
@map.entities.each do |entity| window.director.map.entities.each do |entity|
entity.button_up(id) if defined?(entity.button_up) entity.button_up(id) if defined?(entity.button_up)
end end
end end

View File

@@ -0,0 +1,24 @@
class IMICFPS
class Commands
class RendererInfoCommand < Command
def group
:global
end
def command
:renderer_info
end
def handle(arguments, console)
console.stdin("OpenGL Vendor: #{Style.notice(glGetString(GL_VENDOR))}")
console.stdin("OpenGL Renderer: #{Style.notice(glGetString(GL_RENDERER))}")
console.stdin("OpenGL Version: #{Style.notice(glGetString(GL_VERSION))}")
console.stdin("OpenGL Shader Language Version: #{Style.notice(glGetString(GL_SHADING_LANGUAGE_VERSION))}")
end
def usage
"#{Style.highlight("renderer_info")} #{Style.notice("Returns OpenGL renderer information")}"
end
end
end
end

View File

@@ -1,7 +1,7 @@
class IMICFPS class IMICFPS
class Window < CyberarmEngine::Window class Window < CyberarmEngine::Window
attr_accessor :number_of_vertices, :needs_cursor attr_accessor :number_of_vertices, :needs_cursor
attr_reader :renderer, :scene, :config attr_reader :renderer, :scene, :config, :director
attr_reader :console, :delta_time attr_reader :console, :delta_time
def initialize(window_width = 1280, window_height = 720, fullscreen = false) def initialize(window_width = 1280, window_height = 720, fullscreen = false)
@@ -23,6 +23,8 @@ class IMICFPS
self.caption = "#{IMICFPS::NAME} v#{IMICFPS::VERSION} (#{IMICFPS::RELEASE_NAME})" self.caption = "#{IMICFPS::NAME} v#{IMICFPS::VERSION} (#{IMICFPS::RELEASE_NAME})"
@director = Networking::Director.new
@config = CyberarmEngine::ConfigFile.new(file: IMICFPS::GAME_ROOT_PATH + "/data/config.json") @config = CyberarmEngine::ConfigFile.new(file: IMICFPS::GAME_ROOT_PATH + "/data/config.json")
@show_console = false @show_console = false
@console = Console.new @console = Console.new

View File

@@ -29,8 +29,8 @@ require_all "lib/networking/backend"
Thread.abort_on_exception = true Thread.abort_on_exception = true
server = CyberarmEngine::Networking::Server.new server = CyberarmEngine::Networking::Server.new
def server.client_connected(peer:) def server.peer_connected(peer:)
puts "Client connected as peer: #{peer.id}" puts "Peer connected: #{peer.id}"
end end
def server.packet_received(peer:, message:, channel:) def server.packet_received(peer:, message:, channel:)