Skip to content

rustyscreeps/screeps-async

Repository files navigation

Screeps Async

Tick-aware Async Runtime for Screeps

Crates.io CI docs.rs version badge

Getting Started

Add screeps-async to your Cargo.toml

[dependencies]
screeps-async = "0.3.1"

IMPORTANT: This project is very much in an early stage and significant, breaking changes may be made between alpha versions.

Add #[screeps_async::main] to your main loop function and start spawning async code!

#[screeps_async::main] // must be before other attributes to work properly
#[wasm_bindgen(js_name = loop)]
pub fn game_loop() {
    // Tick logic that spawns some tasks
    screeps_async::spawn(async {
        println!("Hello!");
    });
}

The each_tick! macro

In synchronous Screeps code, one common pattern is to use a state machine and decide what logic to use based off the state. With asynchronous programming you are liberated from the shackles of the tick and free to write a single function whose execution spans across ticks. This allows you to "unroll" a state machine into a single function that execute top-to-bottom. In such cases you will likely still need to write code that loops each tick until moving on (ie repeatedly calling move_to).

However, it is not safe to store references to RoomObjects across ticks as they may be destroyed/go out of vision. The solution to this problem is to store ObjectIds instead and .resolve() them each tick.

The each_tick! macro may be used to help reduce the boilerplate of looping and .resolve()'ing each tick. each_tick! accepts a list of dependencies (usually ObjectIds) and will call .resolve() on them each tick, making the resolved value available to the provided code block under the same name as the dependency (see example for details)

async fn collect_and_upgrade(creep: ObjectId<Creep>, source: ObjectId<Source>, controller: ObjectId<StructureController>) {
    // Collect until full
    each_tick!(creep, source, {
        if creep.store().get_free_capacity(Some(ResourceType::Energy)) == 0 {
            return Some(());
        }
        
        if let Err(_) = creep.harvest(&source) {
            let _ = creep.move_to(&source);
        }
        
        // Do this forever
        None
    }).await.expect("Creep or source is gone!");
    
    each_tick!(creep, controller, {
        if creep.store().get_used_capacity(Some(ResourceType::Energy)) == 0 {
            return Some(());
        }
        
        if let Err(_) = creep.upgrade_controller(&controller) {
            let _ = creep.move_to(&controller);
        }
        
        // Do this forever
        None
    }).await.expect("Creep or controller is gone!");
}