Skip to content

Commit

Permalink
Convert from from cargo-screeps to node/rollup/babel (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
shanemadden authored Aug 27, 2024
1 parent c610edf commit d7a01d7
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 140 deletions.
50 changes: 50 additions & 0 deletions .example-screeps.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
servers:
# Deploy to the main MMO server - note that tokens are
# the only supported auth method for official servers (mmo, season, and ptr)
mmo:
host: screeps.com
secure: true
token: your-auth-token-here
branch: default
# The public test realm can be a good place to test your code
ptr:
host: screeps.com
secure: true
token: your-auth-token-here
path: /ptr
branch: default
# Seasonal server configuration - this environment has unique mechanics each
# season, so it might make sense to have feature flag(s) for different mechanics
season:
host: screeps.com
secure: true
token: your-auth-token-here
path: /season
branch: default
private-server:
host: 127.0.0.1
port: 21025
secure: false
username: user
password: password
branch: default
configs:
# Whether to minify generated javascript for each configured server
terser:
# The special '*'' key sets a default for all servers which
# will be **overridden** by an applicable per-server config
'*': false
ptr: false
localhost: false
# Additional options to pass to wasm-pack to customize the build for each server
wasm-pack-options:
# The special '*'' key sets flags applied to all servers, which
# will be **concatenated** with any applicable per-server config
'*': []
# This setting enables the `mmo` crate feature for these destinations,
# which enables the API functions for intershard communication and pixel
# generation, which are specific to MMO
mmo: ["--features", "mmo"]
ptr: ["--features", "mmo"]
# Other servers can each have their own build flags, including crate features:
#season: ["--features", "my-season-7-feature"]
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Compiled source #
###################
target
dist
pkg

# Dependencies #
################
node_modules

# Packages #
############
Expand Down Expand Up @@ -33,4 +39,7 @@ nbproject
*.iws
*.sublime-project
*.sublime-workspace
screeps.toml

# Config w/ secrets #
#####################
.screeps.yaml
8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,13 @@ opt-level = 3
lto = true

[package.metadata.wasm-pack.profile.release]
# Replace the following to enable wasm-opt optimization
# wasm-pack will try to install wasm-opt automatically, but it must be installed by hand on some
# operating systems.
wasm-opt = false
# See wasm-opt for full available options; handy examples:
# -O4 - optimize aggressively for performance
# -Oz - optimize aggressively for code size
# -g - leave debug info in place, allowing for more descriptive stack traces on panic
# --disable-sign-ext - prevents opcodes that the screeps servers can't load (see
# --signext-lowering - removes opcodes that the screeps servers can't load (see
# https://github.com/rustyscreeps/screeps-game-api/issues/391)
#wasm-opt = ["-O4", "--disable-sign-ext"]
wasm-opt = ["-O4", "--signext-lowering"]

[features]
default = []
Expand Down
79 changes: 61 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,84 @@ Starter Rust AI for [Screeps: World][screeps], the JavaScript-based MMO game.

This uses the [`screeps-game-api`] bindings from the [rustyscreeps] organization.

While it's possible to compile using [`wasm-pack`] directly using the Node.js target,
some modifications are needed to load the output within the Screep environment, so it's
recommended to use [`cargo-screeps`] for building and deploying your code.
[`wasm-pack`] is used for building the Rust code to WebAssembly. This example uses [Rollup] to
bundle the resulting javascript, [Babel] to transpile generated code for compatibility with older
Node.js versions running on the Screeps servers, and the [`screeps-api`] Node.js package to deploy.

The documentation is currently a bit sparse. API docs which list functions one
can use are located at https://docs.rs/screeps-game-api/.
Documentation for the Rust version of the game APIs is at https://docs.rs/screeps-game-api/.

Almost all crates on https://crates.io/ are usable (only things which interact with OS
apis are broken).

Quickstart:
## Quickstart:

```sh
# Install CLI dependency:
cargo install cargo-screeps
# Install rustup: https://rustup.rs/

# Install wasm-pack
cargo install wasm-pack

# Install wasm-opt
cargo install wasm-opt

# Install Node.js for build steps - versions 16 through 22 have been tested, any should work
# nvm is recommended but not required to manage the install, follow instructions at:
# Mac/Linux: https://github.com/nvm-sh/nvm
# Windows: https://github.com/coreybutler/nvm-windows

# Installs Node.js at version 22
nvm install 22
nvm use 22

# Clone the starter
git clone https://github.com/rustyscreeps/screeps-starter-rust.git
cd screeps-starter-rust
# note: if you customize the name of the crate, you'll need to update the MODULE_NAME
# variable in the javascript/main.js file with the updated name
# variable in the js_src/main.js file and the module import with the updated name, as well
# as the "name" in the package.json

# Install dependencies for JS build
npm install

# Copy the example config, and set up at least one deployment mode.
# Configure credentials if you'd like to upload directly, or a directory to copy to
# if you'd prefer to use the game client to deploy:
cp example-screeps.toml screeps.toml
nano screeps.toml

# Compile plus deploy to the configured 'upload' mode; any section name you
# set up in your screeps.toml for different environments and servers can be used
cargo screeps deploy -m upload
cp .example-screeps.yaml .screeps.yaml
nano .screeps.yaml

# compile for a configured server but don't upload
npm run deploy -- --server ptr --dryrun

# compile and upload to a configured server
npm run deploy -- --server mmo
```

## Migration to 0.22

Versions of [`screeps-game-api`] at 0.22 or higher are no longer compatible with the
[`cargo-screeps`] tool for building and deployment; the transpile step being handled by [Babel] is
required to transform the generated JS into code that the game servers can load.

To migrate an existing bot to using the new JavaScript translation layer and deploy script:

- Create a `.screeps.yaml` with the relevant settings from your `screeps.toml` file applied to the
new `.example-screeps.yaml` example file in this repo.
- Add to your `.gitignore`: `.screeps.yaml`, `node_modules`, and `dist`
- Create a `package.json` copied from the one in this repo and make appropriate customizations.
- Install Node.js (from the quickstart steps above), then run `npm install` from within the bot
directory to install the required packages.
- Copy the `deploy.js` script over to a new `js_tools` directory.
- Add `main.js` to a new `js_src` directory, either moved from your existing `javascript` dir and
updated, or freshly copied. If updating, you'll need to change:
- Import formatting, particularly for the wasm module.
- wasm module initialization has changed, requiring two calls to first compile the module,
then to initialize the instance of the module.
- Update your `Cargo.toml` with version `0.22` for `screeps-game-api`
- Run `npm run deploy -- --server ptr --dryrun` to compile for PTR, remove the `--dryrun` to deploy

[screeps]: https://screeps.com/
[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/
[`cargo-screeps`]: https://github.com/rustyscreeps/cargo-screeps/
[Rollup]: https://rollupjs.org/
[Babel]: https://babeljs.io/
[`screeps-api`]: https://github.com/screepers/node-screeps-api
[`screeps-game-api`]: https://github.com/rustyscreeps/screeps-game-api/
[`cargo-screeps`]: https://github.com/rustyscreeps/cargo-screeps/
[rustyscreeps]: https://github.com/rustyscreeps/
44 changes: 0 additions & 44 deletions example-screeps.toml

This file was deleted.

69 changes: 0 additions & 69 deletions javascript/main.js

This file was deleted.

86 changes: 86 additions & 0 deletions js_src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use strict";
import 'fastestsmallesttextencoderdecoder-encodeinto/EncoderDecoderTogether.min.js';

import * as bot from '../pkg/screeps_starter_rust.js';
// replace this with the name of your module
const MODULE_NAME = "screeps_starter_rust";
const BUCKET_BOOT_THRESHOLD = 1500;

// This provides the function `console.error` that wasm_bindgen sometimes expects to exist,
// especially with type checks in debug mode. An alternative is to have this be `function () {}`
// and let the exception handler log the thrown JS exceptions, but there is some additional
// information that wasm_bindgen only passes here.
//
// There is nothing special about this function and it may also be used by any JS/Rust code as a convenience.
function console_error() {
const processedArgs = _.map(arguments, (arg) => {
if (arg instanceof Error) {
// On this version of Node, the `stack` property of errors contains
// the message as well.
return arg.stack;
} else {
return arg;
}
}).join(' ');
console.log("ERROR:", processedArgs);
Game.notify(processedArgs);
}

// track whether running wasm loop for each tick completes, to detect errors or aborted execution
let running = false;

function loaded_loop() {
// need to freshly override the fake console object each tick
console.error = console_error;
if (running) {
// we've had an error on the last tick; skip execution during the current tick, asking to
// have our IVM immediately destroyed so we get a fresh environment next tick;
// workaround for https://github.com/rustwasm/wasm-bindgen/issues/3130
Game.cpu.halt();
} else {
try {
running = true;
bot.loop();
// if execution doesn't get to this point for any reason (error or out-of-CPU
// cancellation), setting to false won't happen which will cause a halt() next tick
running = false;
} catch (error) {
console.log(`caught exception, will halt next tick: ${error}`);
// not logging stack since we've already logged the stack trace from rust via the panic
// hook and that one is generally better, but if we need it, uncomment:

// if (error.stack) {
// console.log("js stack:", error.stack);
// }
}
}
}

// cache for each step of the wasm module's initialization
let wasm_bytes, wasm_module, wasm_instance;

module.exports.loop = function() {
// need to freshly override the fake console object each tick
console.error = console_error;
// temporarily need to polyfill this too because there's a bug causing the warn
// in initSync to fire in bindgen 0.2.93
console.warn = console.log;

// attempt to load the wasm only if there's lots of bucket
if (Game.cpu.bucket < BUCKET_BOOT_THRESHOLD) {
console.log(`startup deferred; ${Game.cpu.bucket} / ${BUCKET_BOOT_THRESHOLD} required bucket`);
return;
}

// run each step of the load process, saving each result so that this can happen over multiple ticks
if (!wasm_bytes) wasm_bytes = require(MODULE_NAME);
if (!wasm_module) wasm_module = new WebAssembly.Module(wasm_bytes);
if (!wasm_instance) wasm_instance = bot.initSync(wasm_module);

// remove the bytes from the heap and require cache, we don't need 'em anymore
wasm_bytes = null;
delete require.cache[MODULE_NAME];
// replace this function with the post-load loop for next tick
module.exports.loop = loaded_loop;
console.log(`loading complete, CPU used: ${Game.cpu.getUsed()}`)
}
Loading

0 comments on commit d7a01d7

Please sign in to comment.