bucketed_game: Old_style_init
[nit.git] / lib / bucketed_game / bucketed_game.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2011-2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Game framework with an emphasis on efficient event coordination
18 #
19 # Provides basic game logic entities to manage a game where the logic is executed by turns:
20 # `Game`, `GameTurn`, `GameEvent`, `Turnable`.
21 # Also offers a bucket system to plan future events at a known number of turns in the future:
22 # `Bucketable` and the services `act_next` and `act_in`.
23 #
24 # Efficiently support large number of entities with rare or sparse actions,
25 # such as a forest with many individual trees growing slowly.
26 module bucketed_game is serialize
27
28 import serialization
29 import counter
30
31 # Something acting on the game
32 abstract class Turnable[G: Game]
33
34 # Execute `turn` for this instance.
35 fun do_turn(turn: GameTurn[G]) is abstract
36 end
37
38 # Something acting on the game from time to time
39 abstract class Bucketable[G: Game]
40 super Turnable[G]
41
42 private var act_at: nullable Int = null
43
44 # Cancel the previously registered acting turn
45 #
46 # Once called, `self.do_turn` will not be invoked until `GameTurn::act_next`
47 # or `GameTurn::act_in` are called again.
48 fun cancel_act do act_at = null
49 end
50
51 # Optimized organization of `Bucketable` instances
52 class Buckets[G: Game]
53 super Turnable[G]
54
55 # Bucket type used in this implementation.
56 type BUCKET: HashSet[Bucketable[G]]
57
58 private var next_bucket: nullable BUCKET = null
59 private var current_bucket_key = -1
60
61 # Number of `buckets`, default at 100
62 #
63 # Must be set prior to using any other methods of this class.
64 var n_buckets = 100
65
66 private var buckets: Array[BUCKET] =
67 [for b in n_buckets.times do new HashSet[Bucketable[G]]] is lazy
68
69 # Stats on delays asked when adding an event with `act_in` and `act_next`
70 private var delays = new Counter[Int]
71
72 # Add the Bucketable event `e` at `at_tick`.
73 fun add_at(e: Bucketable[G], at_tick: Int)
74 do
75 var at_key = key_for_tick(at_tick)
76
77 if at_key == current_bucket_key then
78 next_bucket.as(not null).add(e)
79 else
80 buckets[at_key].add(e)
81 end
82
83 e.act_at = at_tick
84 end
85
86 private fun key_for_tick(at_tick: Int): Int
87 do
88 return at_tick % buckets.length
89 end
90
91 redef fun do_turn(turn: GameTurn[G])
92 do
93 current_bucket_key = key_for_tick(turn.tick)
94 var current_bucket = buckets[current_bucket_key]
95
96 var next_bucket = new HashSet[Bucketable[G]]
97 buckets[current_bucket_key] = next_bucket
98 self.next_bucket = next_bucket
99
100 for e in current_bucket do
101 var act_at = e.act_at
102 if act_at != null then
103 if turn.tick == act_at then
104 e.do_turn(turn)
105 else if act_at > turn.tick and
106 key_for_tick(act_at) == current_bucket_key
107 then
108 next_bucket.add(e)
109 end
110 end
111 end
112 end
113
114 # Get some statistics on both the current held events and historic expired events
115 fun stats: String
116 do
117 var entries = 0
118 var instances = new HashSet[Bucketable[G]]
119 var max = 0
120 var min = 100000
121 for bucket in buckets do
122 var len = bucket.length
123 entries += len
124 instances.add_all bucket
125 min = min.min(len)
126 max = max.max(len)
127 end
128 var avg = entries.to_f / buckets.length.to_f
129
130 return "{buckets.length} buckets; uniq/tot:{instances.length}/{entries}, avg:{avg.to_precision(1)}, min:{min}, max:{max}\n" +
131 "history:{delays.sum}, avg:{delays.avg}, min:{delays[delays.min.as(not null)]}, max:{delays[delays.max.as(not null)]}"
132 end
133 end
134
135 # Game related event
136 interface GameEvent
137
138 # Apply `self` to `game` logic.
139 fun apply( game : ThinGame ) is abstract
140 end
141
142 # Event raised at the first turn
143 class FirstTurnEvent
144 super GameEvent
145 end
146
147 # Game logic on the client
148 class ThinGame
149
150 # Current game tick
151 #
152 # Default is 0.
153 var tick: Int = 0 is writable
154 end
155
156 # Game turn on the client
157 class ThinGameTurn[G: ThinGame]
158
159 # Game tick when happened this turn
160 var tick: Int is protected writable
161
162 # Game events occurred for `self`.
163 var events = new Array[GameEvent] is protected writable
164 end
165
166 # Game turn on the full logic
167 class GameTurn[G: Game]
168 super ThinGameTurn[G]
169
170 # Game that `self` belongs to.
171 var game: G
172
173 # Create a new game turn for `game`.
174 init(game: G)
175 is
176 is_old_style_init
177 do
178 _tick = game.tick
179 _game = game
180 end
181
182 # Insert the Bucketable event `e` to be executed at next tick.
183 fun act_next(e: Bucketable[G])
184 do
185 game.buckets.add_at(e, tick + 1)
186 game.buckets.delays.inc(1)
187 end
188
189 # Insert the Bucketable event `e` to be executed at tick `t`.
190 fun act_in(e: Bucketable[G], t: Int)
191 do
192 game.buckets.add_at(e, tick + t)
193 game.buckets.delays.inc(t)
194 end
195
196 # Add and `apply` a game `event`.
197 fun add_event( event : GameEvent )
198 do
199 event.apply( game )
200 events.add( event )
201 end
202 end
203
204 # Full game logic
205 class Game
206 super ThinGame
207
208 # Game type used in this implementation.
209 type G: Game
210
211 # Bucket list in this game.
212 var buckets: Buckets[G] = new Buckets[G]
213
214 # Last turn executed in this game
215 # Can be used to consult the latest events (by the display for example),
216 # but cannot be used to add new Events.
217 var last_turn: nullable ThinGameTurn[G] = null
218
219 # Execute and return a new GameTurn.
220 #
221 # This method calls `do_pre_turn` before executing the GameTurn
222 # and `do_post_turn` after.
223 fun do_turn: GameTurn[G]
224 do
225 var turn = new GameTurn[G](self)
226
227 do_pre_turn(turn)
228 buckets.do_turn(turn)
229 do_post_turn(turn)
230
231 last_turn = turn
232
233 tick += 1
234
235 return turn
236 end
237
238 # Execute something before executing the current GameTurn.
239 #
240 # Should be redefined by clients to add a pre-turn behavior.
241 # See `Game::do_turn`.
242 fun do_pre_turn(turn: GameTurn[G]) do end
243
244 # Execute something after executing the current GameTurn.
245 #
246 # Should be redefined by clients to add a post-turn behavior.
247 # See `Game::do_turn`.
248 fun do_post_turn(turn: GameTurn[G]) do end
249 end