# Rustidor #1: Porting Sauerbraten from C++ to Rust


This is the first post in a series dedicated to porting the epic FPS game [Sauerbraten](http://www.sauerbraten.org/) from C++ to Rust. In this post, we focus more on setting up the project rather than delving into how to implement C++ functions in Rust.

# Installation

## Development Packages

To get started, open a terminal and install the following packages. I assume you're using an apt-based Linux distro. Users of other package managers should use analogous syntax:

```bash
~$ sudo apt install build-essential git subversion
~$ sudo apt install libsdl2-dev libsdl2-mixer-dev  libsdl2-image-dev
```

## SDL Libraries

These libraries are necessary for the game to run. Install them using the following command:

```bash
~$ sudo apt install libsdl2-2.0-0 libsdl2-mixer-2.0-0 libsdl2-image-2.0-0
```

## Rust installation

- Install Rust using rustup by following the instructions at [https://rustup.rs/](https://rustup.rs/)
- Install Clippy to receive advice on your coding style:

```bash
~$ rustup component add clippy
```

## Codebase
Navigate to the directory where you want to set up this project. For example, let's call it `$RUSTIDOR_WORKING_DIR`, but you can replace it with your preferred name:
```
~$ mkdir $RUSTIDOR_WORKING_DIR
~$ cd $RUSTIDOR_WORKING_DIR
~/$RUSTIDOR_WORKING_DIR$ mkdir sauerbraten_port && cd sauerbraten_port
```
Download the original Sauerbraten code from the official SVN repository. This will provide the images, maps, sounds, etc., which are licensed under a different license than the code:
```bash
~/$RUSTIDOR_WORKING_DIR$ svn checkout svn://svn.code.sf.net/p/sauerbraten/code/@r6852 sauerbraten-code
```
Next, download the `rustidor` code:
```bash
~/$RUSTIDOR_WORKING_DIR$ git clone https://github.com/jsrmalvarez/rustidor.git
```
## Test Sauerbraten

Now, it's time to build and run the game to ensure that we have a solid starting point.

To build the game, execute the following commands:
```bash
~/$RUSTIDOR_WORKING_DIR$ cd sauerbraten-code/src
~/$RUSTIDOR_WORKING_DIR/sauerbraten-code/src$ make
```
Wait for the executable to compile. It should complete without any errors. After that, run the game with:

```bash
~/$RUSTIDOR_WORKING_DIR/sauerbraten-code/src$ cd ..
~/$RUSTIDOR_WORKING_DIR/sauerbraten-code$ src/sauer_client
```

You should see the welcome screen, hear the heavy metal music, and be able to start a bot match, for example. When you're done defeating bots, return to the working directory:
```bash
~/$RUSTIDOR_WORKING_DIR/sauerbraten-code$ cd ..
~/$RUSTIDOR_WORKING_DIR$ 
```

## Linking to non-redistributable assets

Sauerbraten includes images, maps, sounds, etc., which are licensed separately from the code. The game needs these assets to run, which is why we downloaded them from the official repository.

Change to the `rustidor` directory (where we previously cloned the `rustidor` Git repository):

```bash
~/$RUSTIDOR_WORKING_DIR$ cd rustidor
```
Let's checkout the branch for this post

```bash
~/$RUSTIDOR_WORKING_DIR/rustidor$ git checkout post#1
```
Build symbolic links to `data` and `packages` directories:

```bash
~/$RUSTIDOR_WORKING_DIR/rustidor$ ln -s ../sauerbraten-code/data data
~/$RUSTIDOR_WORKING_DIR/rustidor$ ln -s ../sauerbraten-code/packages packages
```

Now, we are all set to start coding!

# Port the first function

We are going to write a Rust static library with the implementation of some functions
and some wrappers to exchange data between C/C++ and Rust. This is needed because the binary data format is not the same.

### Rust projet

Lets create a new rust library project under `src` directory

```bash
~/$RUSTIDOR_WORKING_DIR/rustidor$ cd src
~/$RUSTIDOR_WORKING_DIR/rustidor/src$ cargo new --lib rust_port
     Created library `rust_port` package
~/$RUSTIDOR_WORKING_DIR/rustidor/src$ 
```

Then, edit file `~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port/Cargo.toml` to configure the project as a static library adding the lib section:

```toml
[package]
...
[lib]
name = "rust_port_lib"
crate-type = ["staticlib"]
...
```
Change to rust_port folder if you haven't already and execute cargo build. No errors should appear.
```
~/$RUSTIDOR_WORKING_DIR/rustidor$ cd rust_port
~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port$ cargo build --release
   Compiling rust_port v0.1.0 (~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port)
    Finished release [optimized] target(s) in 0.32s
```

### Lets code!

#### Port the implementation

The first function we are going to port is `game::getweapon` from file `$RUSTIDOR_WORKING_DIR/rustidor/src/fpsgame/weapon.cpp`

```cpp
 int getweapon(const char *name)
 {
     const char *abbrevs[] = { "FI", "SG", "CG", "RL", "RI", "GL", "PI"      };
     if(isdigit(name[0])) return parseint(name);
     else loopi(sizeof(abbrevs)/sizeof(abbrevs[0])) if(!strcasecmp(abbrevs[i], name)) return i;
     return -1;
 }
```

This function takes a null-terminated C string as a parameter. Initially, it checks if the string can be converted into a numerical value and returns that number if the conversion is successful. If the string cannot be converted to a number, the function proceeds to perform a case-insensitive search for the name within the `abbrevs` array. It returns the index of the matching name or -1 if no match is found


Let's implement that functionality à la Rust.
Open `$RUSTIDOR_WORKING_DIR/sauerbraten-code/rustidor/src/rust_port/src/lib.rs`

Remove `add`function and put this one instead:
```rust
fn get_weapon(name: &str) -> Option<usize>{
  if let Ok(res) = name.parse() {
    Some(res)
  }
  else {
    const ABBREVS:[&str;7] = ["FI", "SG", "CG", "RL", "RI", "GL", "PI"];
    ABBREVS.iter().position(|&x| { x == name.to_uppercase() })
  }
}
```

This `get_weapon` rust function takes a string slice with the name of a weapon or a number. It returns an `Option<usize>`. If name can be parsed to an `usize` value it will return that value. If it matches ()case insensitive) with one of the `&str` values in ABBREVS array, it will return the matching index. It will return None otherwhise.

The `get_weapon` Rust function accepts a string slice containing the name of a weapon or a number and returns an `Option<usize>`. If the provided name can be successfully parsed into a `usize` value, it will return that value. However, if the name matches (in a case-insensitive manner) with any of the `&str` values within the `ABBREVS` array, the function will return the corresponding index. In all other cases, it will return `None`.

### C wrapper

C will not use `&str` to call `get_weapon` and it won't accept an `Option<usize>` as return value. We need a wrapper function around `get_weapon` that provides input and output types compatibility.

Add these lines to the beginning of `lib.rs` file:

```rust
use std::os::raw::c_char;
use std::ffi::CStr;

/// # Safety
#[no_mangle]
pub unsafe extern "C" fn rstd_getweapon(ptr: *const c_char) -> i32 {
  let c_str = CStr::from_ptr(ptr);
  let rust_str = c_str.to_str().expect("Bad encoding");
  match get_weapon(rust_str) {
    Some(weapon_index) => i32::try_from(weapon_index).unwrap_or(-1),
    _ => -1
  }
}
```

This `rstd_getweapon` wrapper function receives a type compatible with C `const char*` that can be converted to `&str` to be passed to `get_weapon`. Then, the value returned from `get_weapon` is converted to `i32`, which is (for almost any non-esoteric system) compatible with C's `int` type.

The `# Safety` doc comment is suggested by Clippy, as the function is declared to be unsafe (due to a pointer dereference when calling `CStr::from_ptr`). The `#[no_mangle]` attribute tells the linker to use the function name as is, and the `extern "C"` part makes this function adhere to the C calling convention for our system.

### Test it

Let's add some tests to check that `get_weapon` implementation and to be sure that `rstd_getweapon` behaviour matches the one of the C. At the end of `lib.rs` file, replace the default tests generated by `cargo` with these:

```rust
#[cfg(test)]
mod unit_tests {
    use super::*;

    #[test]
    fn numeric_input() {
        assert_eq!(get_weapon("2"), Some(2));
        assert_eq!(get_weapon("42"), Some(42));
    }

    #[test]
    fn name_input() {
        assert_eq!(get_weapon("FI"), Some(0));
        assert_eq!(get_weapon("sG"), Some(1));
        assert_eq!(get_weapon("cg"), Some(2));
        assert_eq!(get_weapon("RL"), Some(3));
        assert_eq!(get_weapon("RI"), Some(4));
        assert_eq!(get_weapon("Gl"), Some(5));
        assert_eq!(get_weapon("pi"), Some(6));
    }
    #[test]
    fn erroneous_input() {
      assert_eq!(get_weapon(""), None);
      assert_eq!(get_weapon("a"), None);
      assert_eq!(get_weapon("-8"), None);
      assert_eq!(get_weapon("KK"), None);
    }
}

#[cfg(test)]
mod c_integration_tests {

    use super::*;
    use std::ffi::CString;

    #[test]
    fn number_input(){
      let mut c_string:CString;
      c_string = CString::new("0").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 0);
      c_string = CString::new("42").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 42);
    }

    #[test]
    fn name_input(){
      let mut c_string:CString;
      c_string = CString::new("FI").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 0);
      c_string = CString::new("sG").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 1);
      c_string = CString::new("cg").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 2);
      c_string = CString::new("RL").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 3);
      c_string = CString::new("RI").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 4);
      c_string = CString::new("Gl").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 5);
      c_string = CString::new("pi").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, 6);
    }

    #[test]
    fn erroneous_input() {
      let mut c_string:CString;
      c_string = CString::new("").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, -1);
      c_string = CString::new("a").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, -1);
      c_string = CString::new("-8").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, -1);
      c_string = CString::new("KK").unwrap();
      assert_eq!(unsafe{ rstd_getweapon(c_string.as_ptr()) }, -1);
    }
}
```

As you can see, these comprehensive tests verify that the function correctly handles different scenarios: it checks that a numeric input is successfully converted to a number, that an input matching one of the strings assigned to a weapon returns the corresponding index, and that other strings result in the function returning -1.

Let's run the tests. You should see an output similar to this, with all the test passed:
```bash
~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port$ cargo test
   Compiling rust_port v0.1.0 (~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port)
    Finished test [unoptimized + debuginfo] target(s) in 0.22s
     Running unittests src/lib.rs (target/debug/deps/rust_port_lib-223b9f9730e652cb)

running 6 tests
test c_integration_tests::erroneous_input ... ok
test c_integration_tests::number_input ... ok
test unit_tests::name_input ... ok
test unit_tests::erroneous_input ... ok
test unit_tests::numeric_input ... ok
test c_integration_tests::name_input ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

```

### Build the library

Let's build the library. Call `cargo build` and check that `librust_port_lib.a` file is under
 `target/release` directory

```bash
~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port$ cargo build --release
   Compiling rust_port v0.1.0 (~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port)
    Finished release [optimized] target(s) in 0.32s
~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port$ ls target/release
build  deps  examples  incremental  librust_port_lib.a  librust_port_lib.d
```

### Replace C++ code

Now it's time to replace the implementation in C++ code for a call to our function.
Go back to C++ function `game::getweapon` in file `$RUSTIDOR_WORKING_DIR/rustidor/src/fpsgame/weapon.cpp`
and replace the code inside with the call to our function.

```cpp
 int getweapon(const char *name)
 {
     return rstd_getweapon(name);
 }
```
### Create and include new header file

This function is undeclared. We need to declare it in a header. Let's create a include directory:

```bash
~/$RUSTIDOR_WORKING_DIR/rustidor/src/rust_port$ mkdir include 
```

Put this code in a new file under the `include` directory and save it as `rust_port.h`
```cpp
#ifndef RUSTIDOR
#define RUSTIDOR

extern "C" {

int rstd_getweapon(const char* c_char);

}
#endif//RUSTIDOR
```

Now add `#include "rust_port.h"` at the top of `$RUSTIDOR_WORKING_DIR/rustidor/src/fpsgame/weapon.cpp` file.

### Update makefile

Let's edit the Makefile to tell gcc where the new header is and where the new library is.
Edit `$RUSTIDOR_WORKING_DIR/rustidor/src/Makefile` file

The `INCLUDES` line should be updated like this, to add the folder of the header

```make
...
INCLUDES= -Ishared -Iengine -Ifpsgame -Ienet/include -Irust_port/include
...
```

Around line 79, the library directory and the instruction to load the library should be added:

```make
...
endif
endif
CLIENT_LIBS+= -L rust_port/target/release -lrust_port_lib
CLIENT_OBJS= \
	shared/crypto.o \
	shared/geom.o \
...
```

## Test Sauerbraten again
Let's build the game and test it.

```bash
~/$RUSTIDOR_WORKING_DIR$ cd rustidor/src
~/$RUSTIDOR_WORKING_DIR/rustidor/src$ make
```
After the build, launch the game:
```bash
~/$RUSTIDOR_WORKING_DIR/rustidor-code/src$ cd ..
~/$RUSTIDOR_WORKING_DIR/rustidor-code$ src/sauer_client
```
Everything should be as usual. Specially check that you can start a bot match and change weapons (mouse wheel or number keys)

# All the code
Remember that all the code can be found at [rustidor github repository](https://github.com/jsrmalvarez/rustidor/)

