chingu 0.9rc7 → 0.9rc8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|