Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert from from cargo-screeps to node/rollup/babel #59

Merged
merged 18 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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