1 # This file is part of NIT (http://www.nitlanguage.org).
3 # Copyright 2011-2013 Alexis Laferrière <alexis.laf@xymus.net>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Game framework with an emphasis on efficient event coordination
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`.
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
31 # Something acting on the game
32 abstract class Turnable[G
: Game]
34 # Execute `turn` for this instance.
35 fun do_turn
(turn
: GameTurn[G
]) is abstract
38 # Something acting on the game from time to time
39 abstract class Bucketable[G
: Game]
42 private var act_at
: nullable Int = null
44 # Cancel the previously registered acting turn
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
51 # Optimized organization of `Bucketable` instances
52 class Buckets[G
: Game]
55 # Bucket type used in this implementation.
56 type BUCKET: HashSet[Bucketable[G
]]
58 private var next_bucket
: nullable BUCKET = null
59 private var current_bucket_key
: Int = -1
61 # Number of `buckets`, default at 100
63 # Must be set prior to using any other methods of this class.
66 private var buckets
: Array[BUCKET] =
67 [for b
in n_buckets
.times
do new HashSet[Bucketable[G
]]] is lazy
69 # Stats on delays asked when adding an event with `act_in` and `act_next`
70 private var delays
= new Counter[Int]
72 # Add the Bucketable event `e` at `at_tick`.
73 fun add_at
(e
: Bucketable[G
], at_tick
: Int)
75 var at_key
= key_for_tick
(at_tick
)
77 if at_key
== current_bucket_key
then
78 next_bucket
.as(not null).add
(e
)
80 buckets
[at_key
].add
(e
)
86 private fun key_for_tick
(at_tick
: Int): Int
88 return at_tick
% buckets
.length
91 redef fun do_turn
(turn
: GameTurn[G
])
93 current_bucket_key
= key_for_tick
(turn
.tick
)
94 var current_bucket
= buckets
[current_bucket_key
]
96 var next_bucket
= new HashSet[Bucketable[G
]]
97 buckets
[current_bucket_key
] = next_bucket
98 self.next_bucket
= next_bucket
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
105 else if act_at
> turn
.tick
and
106 key_for_tick
(act_at
) == current_bucket_key
114 # Get some statistics on both the current held events and historic expired events
118 var instances
= new HashSet[Bucketable[G
]]
121 for bucket
in buckets
do
122 var len
= bucket
.length
124 instances
.add_all bucket
128 var avg
= entries
.to_f
/ buckets
.length
.to_f
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)]}"
138 # Apply `self` to `game` logic.
139 fun apply
( game
: ThinGame ) is abstract
142 # Event raised at the first turn
147 # Game logic on the client
153 var tick
: Int = 0 is writable
156 # Game turn on the client
157 class ThinGameTurn[G
: ThinGame]
159 # Game tick when happened this turn
160 var tick
: Int is protected writable
162 # Game events occurred for `self`.
163 var events
= new Array[GameEvent] is protected writable
166 # Game turn on the full logic
167 class GameTurn[G
: Game]
168 super ThinGameTurn[G
]
170 # Game that `self` belongs to.
173 # Create a new game turn for `game`.
174 init (game
: G
) is old_style_init
do
179 # Insert the Bucketable event `e` to be executed at next tick.
180 fun act_next
(e
: Bucketable[G
])
182 game
.buckets
.add_at
(e
, tick
+ 1)
183 game
.buckets
.delays
.inc
(1)
186 # Insert the Bucketable event `e` to be executed at tick `t`.
187 fun act_in
(e
: Bucketable[G
], t
: Int)
189 game
.buckets
.add_at
(e
, tick
+ t
)
190 game
.buckets
.delays
.inc
(t
)
193 # Add and `apply` a game `event`.
194 fun add_event
( event
: GameEvent )
205 # Game type used in this implementation.
208 # Bucket list in this game.
209 var buckets
: Buckets[G
] = new Buckets[G
]
211 # Last turn executed in this game
212 # Can be used to consult the latest events (by the display for example),
213 # but cannot be used to add new Events.
214 var last_turn
: nullable ThinGameTurn[G
] = null
216 # Execute and return a new GameTurn.
218 # This method calls `do_pre_turn` before executing the GameTurn
219 # and `do_post_turn` after.
220 fun do_turn
: GameTurn[G
]
222 var turn
= new GameTurn[G
](self)
225 buckets
.do_turn
(turn
)
235 # Execute something before executing the current GameTurn.
237 # Should be redefined by clients to add a pre-turn behavior.
238 # See `Game::do_turn`.
239 fun do_pre_turn
(turn
: GameTurn[G
]) do end
241 # Execute something after executing the current GameTurn.
243 # Should be redefined by clients to add a post-turn behavior.
244 # See `Game::do_turn`.
245 fun do_post_turn
(turn
: GameTurn[G
]) do end