Fibers are amazing

Especially in the context of game dev! In path of exile, there was a league years ago called Breach. Your character would run into a Breach and an expanding aura will glow around it, then monsters started spawning one after another.

On the backend, crystal makes that super easy so you don’t need to fiddle with your main game loop, add variables to track a counter/timer, etc. In my case, I just do spawn start_cosmic_bloom(zone, map_entity) and inside that, have a loop with sleep xx to send the monsters. Doesn’t block the IO stream, and even better, we can pass arguments to it… just incredibly easy!

Another case where you can use them is when doing something similar to Diablo 2’s Baal Run (the boss spawns waves of enemies). Although I have my own class for that because there’s a lot going on but it’s a similar concept

When creating anything on the backend that needs to happen sequentially, they are just fantastic! There are probably countless other things they can be used for too… :O

4 Likes

Fibers have one problem in a context of gamedev. Their state cannot be serialized. So in your example you spawn a fiber, then player press “save&exit” and what would you do? You have to somehow terminate fiber while keeping information about which line is executing (and then somehow resume execution at this line).
This limits uses of fibers to games where saving is not allowed or things that are purely visual.

2 Likes

Good point, I actually never thought about that.

But I think this isn’t too much of a restriction, there are still certain cases where a simple state variable might suffice - or you don’t need to track the state, for example in platformers, where you can only save outside of levels (or at specific save points).

Their state cannot be serialized.

Until now! I’ve created a macro that unrolls AST to state machine, making possible to serialize state of coroutine.
This approach has some limitations (most painful - you have to explicitly declare types of used variables), but should work for many cases.
I still haven’t tried them in practice, so there could be bugs and unforeseen problems, but specs are green.

2 Likes

Hey Konovod! Long time no see. In my code, I just check if any players are in that zone the fiber is running in… break if game.players.size == 0. Since the game is instance based, a players count of zero means no one is the game and it’s empty/removed already. Then I when a game is empty and everyone leaves I have a whacky cleanup method that gets called. And the game instance that is inside my property games = Hash(String, Game).new is removed. I assume the garbage collection stuff takes over from there

My structure looks like this:

games["baal runs-01"].zones[1].players[user_id] = Client

pretty cool and so damn easy to structure around (in my opinion at least). Client is a huge instance that has a bunch of stuff, stores all the players stats, etc:

Summary

class Client
property socket : TCPSocket
property user_id = 0
property username = “”
property last_message_time = 0_i64
property flood_attempts = 0
property in_game = “”
property in_game_obj : Game?
property in_zone_obj : Zone?
property god_mode = false

property flood_bucket = STARTING_FLOOD_BUCKET
property last_message_time2 = {} of String => Int64
property last_zone_transfer_time = 0_i64
property old_x = 0f32
property old_y = 0f32
property x = 0f32
property y = 0f32
property incoming_position_x = 0.00 # when using a skill, the client’s incoming mouse position
property incoming_position_y = 0.00

property width = 70
property height = 70
property angle = 0f32
property local_game_update_buffer = GUBuffer.new

property is_moving = false
property end_char_move = false
property cooldowns = Hash(Int32, Int64).new
property selected_characterid = 0_i64
property selected_char_name = “”
property selected_character_image = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
property authenticated = false
property in_level = DEFAULT_LEVEL
property trade_status = 0_i8
property trading_who = “”
property trading_with = 0
property is_trading = false
property cc = 0_i8
property class_data : RPGClasses
property hardcore = false
property ladder = false
property gold = 0
property custom_helmet_enabled = 0_i8
property custom_helmet_last_hash = “”
property custom_helmet_position = “”
property last_galactic_level_completed = 0
property at_max_life = false
property at_max_mana = false

property last_message_received = 0_i64
property last_item_message_sent = 0_i64
property last_game_tick = 0.00
property blocked = Array(Int32).new

property timeState = “”
property prev_states = “”

property dead = false

property dmg_buffer = “”
property mini_states = “1”
property last_char_state = “”
property last_char_anim = “”

Player moving states

property w = false
property a = false
property s = false
property d = false

Player old_moving states

property old_left = false
property old_right = false
property in_safe_zone = false
property type = “”

Timer counters?

property five_second_counter = 0
property last_time_monster_killed = 0
property recent_exp_gained = 0

Quests

property completed_quests = Array(Int32).new
property active_quests = Hash(Int32, Quest).new

property activated_waypoints = [0]

Player Stats, etc

property equipment = Hash(String, String).new
property increased_move_speed = 1.00
property increased_attack_speed = 1.00
property increased_cast_speed = 1.00

property base_mana = 0
property mana = 10.00
property cmana = 10.00
property mana_regen = 0.00

property base_life = 0
property health = 10.00
property chealth = 10.00
property health_regen = 0.00

Resistances…

property fire_res = 0
property cold_res = 0
property lightning_res = 0
property chance_to_block = 0.0

property min_dmg = 0
property max_dmg = 0

Special modifiers

property mana_on_hit = 0
property health_on_hit = 0

Special Modifiers for physical damage

property tree_i_phys = 0.00

Special Modifiers from skill tree

property str = 0_i16
property dex = 0_i16
property vita = 0_i16
property energy = 0_i16

property chance_to_hit = 0.00

property crit_chance = 0.05

property base_crit_multiplier = BASE_CRIT_MULTIPLIER
property crit_multiplier = 0.75

property experience = 0_i64
property experience_needed = 0_i64
property cexperience = 0_i64
property added_experience_gained = 0.00
property level = 0_i8
property skill_points = 0
property stat_points = 0_i16
property skill_list = Array(Int32).new
property skills = Hash(Int64, Hash(String, Hash(String, Float64) | Int64 | Array(Int64))).new
property default_skills = Hash(Int64, Hash(String, Int64)).new
property active_skills = Hash(Int64, ActiveSkill).new

property items = Hash(Int64, ItemTuple).new

property stash = Hash(Int32, Tab).new
property equipped_items = Hash(Int64, ItemTuple).new
property equipped_skill_gems = Hash(Int64, Int64).new

property selected_tabid = 0

Flasks

property flask_update = Array(Int32).new
property flasks = Hash(Int32, Flask).new

property flask_0_charges = 3_i8
property flask_0_active = false
property flask_0_timer = 0.00

property flask_1_charges = 3_i8
property flask_1_active = false

Buffs

property active_buffs = Hash(Int32, Buff).new

property frenzy_charges = 0
property max_frenzy_charges = 3
property chance_to_gain_frenzy_on_kill = 0.00
property chance_to_gain_frenzy_on_hit = 1.00
property at_max_frenzy_charges = false
property max_frenzy_charge_duration = DEFAULT_FRENZY_DURATION
property current_frenzy_charge_duration = 0
property max_traps = 2

property defense = 0

Used to store items when selling to npc (to calculate what the player will receive)

property sell_buffer = Array(Int64).new

property inventory = Array(Array(Int32)).new

property planet_pos_x = 0
property planet_pos_y = 0

property grid_pos_x = 0
property grid_pos_y = 0
property previous_grid_pos_x = 0
property previous_grid_pos_y = 0

Scent trail… used for monster AI?

property scent_trail = Array(ScentTrail).new
property scent_timer = 0

Custom Buffs from skills

property skill_buffs = Hash(String, Hash(String, Float64)).new # name and if active or not, lol

Skill Tree Properties?

property increased_hp_percent = 1.0_f32

You could also kill the loop inside a fiber with a instance variable being set to true and call it whenever. I think I do some of that jiggery-pokery stuff in some other code somewhere

Maybe I’m using fibers more on the high level side, as I view them like a little setInterval function from JavaScript. That’s easily cancelable whenever I need to. I don’t know that much low level programming, as you can probably tell :P

yes, for online game you shouldn’t have problems - no need to save state because server is always running (and if it is restarted all instances are closed anyway).

I was thinking about offline game where player can save his game, close it, the reopen and expect it to run from the same state.