chingu 0.9rc7 → 0.9rc8
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/example15_trait_timer2.rb +1 -1
- data/examples/example7_gfx_helpers.rb +23 -11
- data/examples/game1.rb +299 -15
- data/examples/high_score_list.yml +22 -22
- data/examples/media/city3.png +0 -0
- data/examples/media/enemy_plane.png +0 -0
- data/lib/chingu.rb +1 -1
- data/lib/chingu/animation.rb +58 -38
- data/lib/chingu/game_state_manager.rb +11 -41
- data/lib/chingu/game_states/edit.rb +5 -2
- data/lib/chingu/game_states/enter_name.rb +3 -3
- data/lib/chingu/game_states/network_client.rb +88 -67
- data/lib/chingu/game_states/network_server.rb +66 -103
- data/lib/chingu/game_states/network_state.rb +71 -0
- data/lib/chingu/gosu_ext/sample.rb +79 -0
- data/lib/chingu/gosu_ext/song.rb +96 -0
- data/lib/chingu/helpers/game_object.rb +5 -1
- data/lib/chingu/helpers/game_state.rb +2 -0
- data/lib/chingu/simple_menu.rb +2 -2
- data/lib/chingu/text.rb +25 -10
- data/lib/chingu/traits/sprite.rb +3 -3
- data/lib/chingu/viewport.rb +18 -14
- data/lib/chingu/window.rb +54 -0
- data/spec/chingu/animation_spec.rb +41 -3
- data/spec/chingu/game_state_manager_spec.rb +50 -3
- data/spec/chingu/network_spec.rb +144 -11
- metadata +20 -15
@@ -1,34 +1,34 @@
|
|
1
1
|
---
|
2
|
-
- :
|
3
|
-
:score: 10197
|
2
|
+
- :score: 10197
|
4
3
|
:text: from example13.rb
|
5
|
-
|
6
|
-
|
4
|
+
:name: NEW
|
5
|
+
- :score: 10187
|
7
6
|
:text: from example13.rb
|
8
|
-
|
9
|
-
|
7
|
+
:name: NEW
|
8
|
+
- :score: 10177
|
10
9
|
:text: from example13.rb
|
11
|
-
|
12
|
-
|
10
|
+
:name: NEW
|
11
|
+
- :score: 10167
|
13
12
|
:text: from example13.rb
|
14
|
-
|
15
|
-
|
13
|
+
:name: NEW
|
14
|
+
- :score: 10157
|
16
15
|
:text: from example13.rb
|
17
|
-
|
18
|
-
|
16
|
+
:name: NEW
|
17
|
+
- :score: 10147
|
19
18
|
:text: from example13.rb
|
20
|
-
|
21
|
-
|
19
|
+
:name: NEW
|
20
|
+
- :score: 10137
|
22
21
|
:text: from example13.rb
|
23
|
-
|
24
|
-
|
22
|
+
:name: NEW
|
23
|
+
- :score: 10127
|
25
24
|
:text: from example13.rb
|
26
|
-
|
27
|
-
|
25
|
+
:name: NEW
|
26
|
+
- :score: 10117
|
28
27
|
:text: from example13.rb
|
29
|
-
|
30
|
-
|
28
|
+
:name: NEW
|
29
|
+
- :score: 10112
|
31
30
|
:text: from example13.rb
|
32
|
-
|
33
|
-
|
31
|
+
:name: NEW
|
32
|
+
- :score: 10107
|
34
33
|
:text: from example13.rb
|
34
|
+
:name: NEW
|
Binary file
|
Binary file
|
data/lib/chingu.rb
CHANGED
data/lib/chingu/animation.rb
CHANGED
@@ -14,10 +14,13 @@ module Chingu
|
|
14
14
|
|
15
15
|
#
|
16
16
|
# Create a new Animation.
|
17
|
+
# Must use :file OR :frames OR :image to create it.
|
17
18
|
#
|
18
19
|
# - loop: [true|false]. After the last frame is used, start from the beginning.
|
19
20
|
# - bounce: [true|false]. After the last frame is used, play it backwards untill the first frame is used again, then start playing forwards again.
|
20
21
|
# - file: Tile-file to cut up animation frames from. Could be a full path or just a name -- then it will look for media_path(file)
|
22
|
+
# - frames: Creates the animation from existing images which are the same size (Array<Gosu::Image>)
|
23
|
+
# - image: Image containing a strip of frames for the animation (Gosu::Image)
|
21
24
|
# - width: width of each frame in the tileanimation
|
22
25
|
# - height: width of each frame in the tileanimation
|
23
26
|
# - size: [width, height]-Array or just one fixnum which will spez both height and width
|
@@ -25,54 +28,71 @@ module Chingu
|
|
25
28
|
# - step: [steps] move animation forward [steps] frames each time we call #next
|
26
29
|
#
|
27
30
|
def initialize(options)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
options = {:step => 1, :loop => true, :bounce => false, :index => 0, :delay => 100}.merge!(options)
|
32
|
+
|
31
33
|
@loop = options[:loop]
|
32
34
|
@bounce = options[:bounce]
|
33
|
-
|
35
|
+
file = options[:file]
|
36
|
+
image = options[:image]
|
37
|
+
@frames = options[:frames]
|
34
38
|
@index = options[:index]
|
35
39
|
@delay = options[:delay]
|
36
40
|
@step = options[:step] || 1
|
37
41
|
@dt = 0
|
38
|
-
|
42
|
+
|
39
43
|
@sub_animations = {}
|
40
44
|
@frame_actions = []
|
41
45
|
|
42
|
-
unless
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
raise ArgumentError, "Must provide one of :frames, :image OR :file parameter" unless [@frames, file, image].compact.size == 1
|
47
|
+
|
48
|
+
if @frames
|
49
|
+
raise ArgumentError, "Must provide at least one frame image with :frames" if @frames.empty?
|
50
|
+
raise ArgumentError, ":frames must consist of images only" unless @frames.all? {|i| i.is_a? Gosu::Image }
|
51
|
+
|
52
|
+
@width, @height = @frames[0].width, @frames[0].height
|
53
|
+
|
54
|
+
raise ArgumentError, ":frames must be of identical size" unless @frames[1..-1].all? {|i| i.width == @width and i.height == @height }
|
55
|
+
|
56
|
+
else
|
57
|
+
if file and not File.exists?(file)
|
58
|
+
Gosu::Image.autoload_dirs.each do |autoload_dir|
|
59
|
+
full_path = File.join(autoload_dir, file)
|
60
|
+
if File.exists?(full_path)
|
61
|
+
file = full_path
|
62
|
+
break
|
63
|
+
end
|
64
|
+
end
|
49
65
|
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Various ways of determening the framesize
|
69
|
+
#
|
70
|
+
if options[:height] && options[:width]
|
71
|
+
@height = options[:height]
|
72
|
+
@width = options[:width]
|
73
|
+
elsif options[:size] && options[:size].is_a?(Array)
|
74
|
+
@width = options[:size][0]
|
75
|
+
@height = options[:size][1]
|
76
|
+
elsif options[:size]
|
77
|
+
@width = options[:size]
|
78
|
+
@height = options[:size]
|
79
|
+
elsif file
|
80
|
+
if file =~ /_(\d+)x(\d+)/
|
81
|
+
# Auto-detect width/height from filename
|
82
|
+
# Tilefile foo_10x25.png would mean frame width 10px and height 25px
|
83
|
+
@width = $1.to_i
|
84
|
+
@height = $2.to_i
|
85
|
+
else
|
86
|
+
# Assume the shortest side of the actual file is the width/height for each frame
|
87
|
+
image = Gosu::Image.new($window, file)
|
88
|
+
@width = @height = (image.width < image.height) ? image.width : image.height
|
89
|
+
end
|
90
|
+
else
|
91
|
+
@width = @height = (image.width < image.height) ? image.width : image.height
|
92
|
+
end
|
93
|
+
|
94
|
+
@frames = Gosu::Image.load_tiles($window, image || file, @width, @height, true)
|
50
95
|
end
|
51
|
-
|
52
|
-
#
|
53
|
-
# Various ways of determening the framesize
|
54
|
-
#
|
55
|
-
if options[:height] && options[:width]
|
56
|
-
@height = options[:height]
|
57
|
-
@width = options[:width]
|
58
|
-
elsif options[:size] && options[:size].is_a?(Array)
|
59
|
-
@width = options[:size][0]
|
60
|
-
@height = options[:size][1]
|
61
|
-
elsif options[:size]
|
62
|
-
@width = options[:size]
|
63
|
-
@height = options[:size]
|
64
|
-
elsif @file =~ /_(\d+)x(\d+)/
|
65
|
-
# Auto-detect width/height from filename
|
66
|
-
# Tilefile foo_10x25.png would mean frame width 10px and height 25px
|
67
|
-
@width = $1.to_i
|
68
|
-
@height = $2.to_i
|
69
|
-
else
|
70
|
-
# Assume the shortest side is the width/height for each frame
|
71
|
-
@image = Gosu::Image.new($window, @file)
|
72
|
-
@width = @height = (@image.width < @image.height) ? @image.width : @image.height
|
73
|
-
end
|
74
|
-
|
75
|
-
@frames = Gosu::Image.load_tiles($window, @file, @width, @height, true)
|
76
96
|
end
|
77
97
|
|
78
98
|
#
|
@@ -247,4 +267,4 @@ module Chingu
|
|
247
267
|
end
|
248
268
|
end
|
249
269
|
end
|
250
|
-
end
|
270
|
+
end
|
@@ -97,43 +97,13 @@ module Chingu
|
|
97
97
|
# Switch to a given game state, _replacing_ the current active one.
|
98
98
|
# By default setup() is called on the game state we're switching _to_.
|
99
99
|
# .. and finalize() is called on the game state we're switching _from_.
|
100
|
-
#
|
100
|
+
#
|
101
101
|
def switch_game_state(state, options = {})
|
102
|
-
options = {:setup => true, :finalize => true, :transitional => true}.merge(options)
|
103
|
-
|
104
|
-
new_state = game_state_instance(state)
|
102
|
+
options = {:setup => true, :finalize => true, :transitional => true}.merge!(options)
|
105
103
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# Give the soon-to-be-disabled state a chance to clean up by calling finalize() on it.
|
111
|
-
current_game_state.finalize if current_game_state.respond_to?(:finalize) && options[:finalize]
|
112
|
-
|
113
|
-
# So BasicGameObject#create connects object to new state in its setup()
|
114
|
-
# Is this doubled in GameState.initialize() ?
|
115
|
-
self.inside_state = new_state
|
116
|
-
|
117
|
-
# Call setup
|
118
|
-
new_state.setup if new_state.respond_to?(:setup) && options[:setup]
|
119
|
-
|
120
|
-
if @transitional_game_state && options[:transitional]
|
121
|
-
# If we have a transitional, switch to that instead, with new_state as first argument
|
122
|
-
transitional_game_state = @transitional_game_state.new(new_state, @transitional_game_state_options)
|
123
|
-
transitional_game_state.game_state_manager = self
|
124
|
-
self.push_game_state(transitional_game_state, :transitional => false)
|
125
|
-
else
|
126
|
-
if current_game_state.nil?
|
127
|
-
@game_states << new_state
|
128
|
-
else
|
129
|
-
# Replace last (active) state with new one
|
130
|
-
@game_states[-1] = new_state
|
131
|
-
end
|
132
|
-
end
|
133
|
-
## MOVED: self.inside_state = current_game_state
|
134
|
-
end
|
135
|
-
|
136
|
-
self.inside_state = nil # no longer 'inside' (as in within initialize() etc) a game state
|
104
|
+
# Don't setup or finalize the underlying state, since it never becomes active.
|
105
|
+
pop_game_state(options.merge(:setup => false))
|
106
|
+
push_game_state(state, options.merge(:finalize => false))
|
137
107
|
end
|
138
108
|
alias :switch :switch_game_state
|
139
109
|
|
@@ -233,18 +203,18 @@ module Chingu
|
|
233
203
|
alias :clear :clear_game_states
|
234
204
|
|
235
205
|
#
|
236
|
-
# Pops through all game states until matching a given game state
|
237
|
-
#
|
238
|
-
def pop_until_game_state(new_state)
|
206
|
+
# Pops through all game states until matching a given game state (takes either a class or instance to match).
|
207
|
+
#
|
208
|
+
def pop_until_game_state(new_state, options = {})
|
239
209
|
if new_state.is_a? Class
|
240
|
-
raise ArgumentError, "No state of given class is on the stack" unless @game_states.
|
210
|
+
raise ArgumentError, "No state of given class is on the stack" unless @game_states.any? {|s| s.is_a? new_state }
|
241
211
|
|
242
|
-
|
212
|
+
pop_game_state(options) until current_game_state.is_a? new_state
|
243
213
|
|
244
214
|
else
|
245
215
|
raise ArgumentError, "State is not on the stack" unless @game_states.include? new_state
|
246
216
|
|
247
|
-
|
217
|
+
pop_game_state(options) until current_game_state == new_state
|
248
218
|
end
|
249
219
|
end
|
250
220
|
|
@@ -136,6 +136,10 @@ module Chingu
|
|
136
136
|
|
137
137
|
@hud_height = 140
|
138
138
|
@toolbar_icon_size = [32,32]
|
139
|
+
draw_toolbar_objects
|
140
|
+
end
|
141
|
+
|
142
|
+
def draw_toolbar_objects
|
139
143
|
x = 20
|
140
144
|
y = 60
|
141
145
|
@classes.each do |klass|
|
@@ -162,9 +166,8 @@ module Chingu
|
|
162
166
|
rescue
|
163
167
|
puts "Couldn't use #{klass} in editor: #{$!}"
|
164
168
|
end
|
165
|
-
end
|
169
|
+
end
|
166
170
|
end
|
167
|
-
|
168
171
|
def display_help
|
169
172
|
text = <<END_OF_STRING
|
170
173
|
F1: This help screen
|
@@ -63,7 +63,7 @@ module Chingu
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
@texts[@index].color = Color::RED
|
66
|
+
@texts[@index].color = ::Gosu::Color::RED
|
67
67
|
@name = Text.create("", :rotaion_center => :top_center, :x => $window.width/2, :y => 60, :size => 80)
|
68
68
|
end
|
69
69
|
|
@@ -94,8 +94,8 @@ module Chingu
|
|
94
94
|
new_value = @index + amount
|
95
95
|
@index = new_value if new_value < @letters.size && new_value >= 0
|
96
96
|
|
97
|
-
@texts.each { |text| text.color = Color::WHITE }
|
98
|
-
@texts[@index].color = Color::RED
|
97
|
+
@texts.each { |text| text.color = ::Gosu::Color::WHITE }
|
98
|
+
@texts[@index].color = ::Gosu::Color::RED
|
99
99
|
|
100
100
|
sleep(0.15)
|
101
101
|
end
|
@@ -18,20 +18,19 @@
|
|
18
18
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
19
19
|
#
|
20
20
|
#++
|
21
|
-
|
22
21
|
module Chingu
|
23
22
|
module GameStates
|
24
23
|
#
|
25
24
|
# A game state for a client in a multiplayer game, suitable for smaller/middle sized games.
|
26
25
|
# Used in combination with game state NetworkServer.
|
27
26
|
#
|
28
|
-
# Uses
|
27
|
+
# Uses non-blocking polling TCP and marshal to communicate.
|
29
28
|
# If your game state inherits from NetworkClient you'll have the following methods available:
|
30
29
|
#
|
31
|
-
# connect(
|
32
|
-
# send_data(data) # Send raw data on the network,
|
33
|
-
# send_msg(whatever ruby data) # Will get
|
34
|
-
# handle_incoming_data(max_size) #
|
30
|
+
# connect(address, port) # Start a non-blocking connection. only connect() uses previosly given ip:port
|
31
|
+
# send_data(data) # Send raw data on the network, non-blocking
|
32
|
+
# send_msg(whatever ruby data) # Will get marshalled and sent to server
|
33
|
+
# handle_incoming_data(max_size) # Non-blocking read of incoming server data
|
35
34
|
# disconnect_from_server # Shuts down all network connections
|
36
35
|
#
|
37
36
|
# The following callbacks can be overwritten to add your game logic:
|
@@ -46,7 +45,7 @@ module Chingu
|
|
46
45
|
# PlayState < Chingu::GameStates::NetworkClient
|
47
46
|
# def initialize(options = {})
|
48
47
|
# super # this is always needed!
|
49
|
-
# connect(options[:
|
48
|
+
# connect(options[:address], options[:port])
|
50
49
|
# end
|
51
50
|
#
|
52
51
|
# def on_connect
|
@@ -65,29 +64,25 @@ module Chingu
|
|
65
64
|
#
|
66
65
|
# So why not EventMachine? No doubt in my mind that EventMachine is a hell of a library Chingu rolls its own for 2 reasons:
|
67
66
|
#
|
68
|
-
# AFAIK EventMachine can be hard to
|
69
|
-
# Rubys
|
67
|
+
# AFAIK EventMachine can be hard to integrate with the classic game loop, event machine wants its own loop
|
68
|
+
# Rubys non-blocking sockets work, so why not keep it simple
|
70
69
|
#
|
71
70
|
#
|
72
|
-
class NetworkClient <
|
73
|
-
attr_reader :
|
74
|
-
alias_method :address, :ip
|
71
|
+
class NetworkClient < NetworkState
|
72
|
+
attr_reader :socket, :timeout
|
75
73
|
|
76
74
|
def connected?; @connected; end
|
77
75
|
|
78
76
|
def initialize(options = {})
|
79
|
-
super
|
80
|
-
|
81
|
-
@
|
82
|
-
|
83
|
-
@port = options[:port] || NetworkServer::DEFAULT_PORT
|
77
|
+
super(options)
|
78
|
+
|
79
|
+
@timeout = options[:timeout] || 4000
|
80
|
+
|
84
81
|
@max_read_per_update = options[:max_read_per_update] || 50000
|
85
82
|
|
86
83
|
@socket = nil
|
87
84
|
@connected = false
|
88
|
-
@
|
89
|
-
@packet_counter = 0
|
90
|
-
@packet_buffer = NetworkServer::PacketBuffer.new
|
85
|
+
@packet_buffer = PacketBuffer.new
|
91
86
|
end
|
92
87
|
|
93
88
|
#
|
@@ -98,47 +93,56 @@ module Chingu
|
|
98
93
|
# 4) #on_data(data) will call #on_msgs(msg)
|
99
94
|
#
|
100
95
|
def update
|
101
|
-
|
96
|
+
|
102
97
|
if @socket and not @connected
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
98
|
+
if Gosu::milliseconds >= @connect_times_out_at
|
99
|
+
@socket = nil
|
100
|
+
on_timeout
|
101
|
+
else
|
102
|
+
begin
|
103
|
+
# Start/Check on our nonblocking tcp connection
|
104
|
+
@socket.connect_nonblock(@sockaddr)
|
105
|
+
rescue Errno::EINPROGRESS #rescue IO::WaitWritable
|
106
|
+
rescue Errno::EALREADY
|
107
|
+
rescue Errno::EISCONN
|
108
|
+
@connected = true
|
109
|
+
on_connect
|
110
|
+
rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EINVAL
|
109
111
|
@socket = nil
|
110
112
|
on_connection_refused
|
113
|
+
rescue Errno::ETIMEDOUT
|
114
|
+
@socket = nil
|
115
|
+
on_timeout
|
111
116
|
end
|
112
|
-
rescue Errno::EISCONN
|
113
|
-
@connected = true
|
114
|
-
on_connect
|
115
|
-
rescue Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EINVAL
|
116
|
-
@socket = nil
|
117
|
-
on_connection_refused
|
118
|
-
rescue Errno::ETIMEDOUT
|
119
|
-
@socket = nil
|
120
|
-
on_timeout
|
121
117
|
end
|
122
118
|
end
|
123
119
|
|
124
|
-
handle_incoming_data
|
120
|
+
handle_incoming_data if @connected
|
121
|
+
|
125
122
|
super
|
126
123
|
end
|
127
124
|
|
128
125
|
#
|
129
|
-
# Connect to a given
|
130
|
-
# Connect is done in a blocking manner.
|
131
|
-
#
|
132
|
-
|
133
|
-
|
126
|
+
# Connect to a given address:port (the server)
|
127
|
+
# Connect is done in a non-blocking manner.
|
128
|
+
# May pass :address and :port, which will overwrite any existing values.
|
129
|
+
def connect(options = {})
|
130
|
+
options = {
|
131
|
+
:address => @address,
|
132
|
+
:port => @port,
|
133
|
+
:reconnect => false, # Doesn't reset the timeout timer; used internally.
|
134
|
+
}.merge! options
|
135
|
+
|
134
136
|
return if @socket
|
135
137
|
|
136
|
-
@
|
137
|
-
@port = port
|
138
|
+
@address = options[:address]
|
139
|
+
@port = options[:port]
|
138
140
|
|
139
141
|
# Set up our @socket, update() will handle the actual nonblocking connection
|
140
142
|
@socket = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
|
141
|
-
@sockaddr = Socket.sockaddr_in(@port, @
|
143
|
+
@sockaddr = Socket.sockaddr_in(@port, @address)
|
144
|
+
|
145
|
+
@connect_times_out_at = Gosu::milliseconds + @timeout unless options[:reconnect]
|
142
146
|
|
143
147
|
return self
|
144
148
|
end
|
@@ -147,23 +151,22 @@ module Chingu
|
|
147
151
|
# Called when connect() fails with connection refused (closed port)
|
148
152
|
#
|
149
153
|
def on_connection_refused
|
150
|
-
puts "[on_connection_refused() #{@
|
151
|
-
connect(
|
154
|
+
puts "[on_connection_refused() #{@address}:#{@port}]" if @debug
|
155
|
+
connect(:reconnect => true)
|
152
156
|
end
|
153
157
|
|
154
158
|
#
|
155
159
|
# Called when connect() recieves no initial answer from server
|
156
160
|
#
|
157
161
|
def on_timeout
|
158
|
-
puts "[on_timeout() #{@
|
159
|
-
connect(@ip, @port)
|
162
|
+
puts "[on_timeout() #{@address}:#{@port}]" if @debug
|
160
163
|
end
|
161
164
|
|
162
165
|
#
|
163
166
|
# on_connect will be called when client successfully makes a connection to server
|
164
167
|
#
|
165
168
|
def on_connect
|
166
|
-
puts "[Connected to Server #{@
|
169
|
+
puts "[Connected to Server #{@address}:#{@port}]" if @debug
|
167
170
|
end
|
168
171
|
|
169
172
|
#
|
@@ -178,16 +181,14 @@ module Chingu
|
|
178
181
|
# handle_incoming_data will call on_data(raw_data) when stuff comes on on the socket.
|
179
182
|
#
|
180
183
|
def handle_incoming_data(amount = @max_read_per_update)
|
181
|
-
return unless @socket
|
184
|
+
return unless @socket and connected?
|
182
185
|
|
183
186
|
if IO.select([@socket], nil, nil, 0.0)
|
184
187
|
begin
|
185
188
|
packet, sender = @socket.recvfrom(amount)
|
186
189
|
on_data(packet)
|
187
190
|
rescue Errno::ECONNABORTED, Errno::ECONNRESET
|
188
|
-
|
189
|
-
@socket = nil
|
190
|
-
on_disconnect
|
191
|
+
disconnect_from_server
|
191
192
|
end
|
192
193
|
end
|
193
194
|
end
|
@@ -198,14 +199,26 @@ module Chingu
|
|
198
199
|
def on_data(data)
|
199
200
|
@packet_buffer.buffer_data data
|
200
201
|
|
202
|
+
@bytes_received += data.length
|
203
|
+
|
201
204
|
while packet = @packet_buffer.next_packet
|
202
|
-
|
205
|
+
@packets_received += 1
|
206
|
+
begin
|
207
|
+
on_msg(Marshal.load(packet))
|
208
|
+
rescue TypeError
|
209
|
+
disconnect_from_server
|
210
|
+
break
|
211
|
+
end
|
203
212
|
end
|
204
213
|
end
|
214
|
+
|
215
|
+
# Handler when message packets are received. Should be overriden in your code.
|
216
|
+
def on_msg(packet)
|
217
|
+
# should be overridden.
|
218
|
+
end
|
205
219
|
|
206
220
|
#
|
207
221
|
# Send a msg to the server
|
208
|
-
# Can be whatever ruby-structure that responds to #to_yaml
|
209
222
|
#
|
210
223
|
def send_msg(msg)
|
211
224
|
send_data(Marshal.dump(msg))
|
@@ -213,28 +226,36 @@ module Chingu
|
|
213
226
|
|
214
227
|
#
|
215
228
|
# Send whatever raw data to the server
|
216
|
-
#
|
229
|
+
# Returns amount of data sent, including header.
|
217
230
|
def send_data(data)
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
231
|
+
length = @socket.write([data.length].pack(NetworkServer::PACKET_HEADER_FORMAT))
|
232
|
+
length += @socket.write(data)
|
233
|
+
@packets_sent += 1
|
234
|
+
@bytes_sent += length
|
235
|
+
length
|
236
|
+
rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, Errno::ENOTCONN
|
237
|
+
disconnect_from_server
|
238
|
+
0
|
226
239
|
end
|
227
240
|
|
228
241
|
# Ensure that the buffer is cleared of data to write (call at the end of update or, at least after all sends).
|
229
242
|
def flush
|
230
|
-
@socket.flush
|
243
|
+
@socket.flush if @socket
|
244
|
+
rescue IOError
|
245
|
+
disconnect_from_server
|
231
246
|
end
|
232
247
|
|
233
248
|
#
|
234
249
|
# Shuts down all communication (closes socket) with server
|
235
250
|
#
|
236
251
|
def disconnect_from_server
|
237
|
-
@socket.close
|
252
|
+
@socket.close if @socket and not @socket.closed?
|
253
|
+
rescue Errno::ENOTCONN
|
254
|
+
ensure
|
255
|
+
@socket = nil
|
256
|
+
was_connected = @connected
|
257
|
+
@connected = false
|
258
|
+
on_disconnect if was_connected
|
238
259
|
end
|
239
260
|
alias close disconnect_from_server
|
240
261
|
alias stop disconnect_from_server
|