The broad, general steps are:
This doc also includes instructions for using assets on-device.
cargo new --lib ios-rust-sdl2
Also if you haven’t ever built rust for ios before, be sure to add the aarch64-apple-ios target
rustup target add aarch64-apple-ios
Add this to the Cargo.toml
[lib]
crate-type = ["lib", "staticlib"]
Also, add some additional dependencies. Most likely you’ll want to update the versions used here You can use path = “../rafx/rafx” instead of version if you prefer
[dependencies]
# use the metal backend of rafx
rafx = { version = "0.1", features = ["rafx-metal", "assets"] }
# use SDL2.. however we need an unpublished iOS raw-window-handle support which as of this writing isn't in a published build
# https://github.com/Rust-SDL2/rust-sdl2/commit/fc05a35896df8b9aa62e61f9fe672ddd6c685def
# NOTE: DONT use static-link feature, we will static link SDL2 ourselves in xcode later
sdl2 = { git = "https://github.com/Rust-SDL2/rust-sdl2.git", rev = "fc05a35896df8b9aa62e61f9fe672ddd6c685def", features = ["raw-window-handle"] }
# Interop with iOS
objc = { version = "0.2.4", features = ["exception"] }
objc-foundation = "0.1"
cocoa-foundation = "0.1.0"
objc_id = "0.1"
libc = "0.2"
# Logging
env_logger = "0.6"
log="0.4"
This will set it up as a git submodule
git submodule add --name SDL2 https://github.com/spurious/SDL-mirror.git SDL2
We will add a script to xcode that will force it to build so don’t worry about that now
We can use xcodegen to make this a lot easier. Fastest way to install is
brew install xcodegen
.
Create a folder xcode in the root of your rust crate (next to Cargo.toml)
# This assumes a directory structure like this:
# root
# SDL2 # git clone of https://github.com/spurious/SDL-mirror.git
# xcode
# project.yml # THIS FILE
# src
# main.cpp
# Cargo.toml #rust lib, with [lib] crate-type = ["lib", "staticlib"]
# src (rust code)
name: RustSdl2App
options:
bundleIdPrefix: # <-- Provide your own prefix like com.yourcompanyname
createIntermediateGroups: true
usesTabs: false
indentWidth: 4
tabWidth: 4
deploymentTarget:
iOS: "12.0"
settings:
CLANG_CXX_LANGUAGE_STANDARD: c++11
CLANG_CXX_LIBRARY: libc++
GCC_C_LANGUAGE_STANDARD: c11
CLANG_WARN_DOCUMENTATION_COMMENTS: false
targets:
RustSdl2App:
type: application
platform: iOS
info:
path: Generated/Info.plist
properties:
LSRequiresIPhoneOS: true
UIRequiredDeviceCapabilities: [arm64]
UIRequiresFullScreen: true
UIStatusBarHidden: true
UISupportedInterfaceOrientations: [UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight]. # <-- Landscape only
UILaunchStoryboardName: LaunchScreen # <-- Maybe this can be removed?
entitlements:
path: Generated/app.entitlements
sources:
- src # <-- This just holds a single main.cpp that calls into rust
# You can add more data to deploy to device like this
# - path: ../custom_data_file.txt
# type: data
# buildPhase: resources
# - path: assets
# type: folder
# buildPhase: resources
settings:
# DEVELOPMENT_TEAM: XXXXXXXXXX <-- Provide your own ID or remove this line and do it manually in the UI.
# (If you don't know it, do it manually in the UI and open your .xcproj in a text editor to find it)
ENABLE_BITCODE: false
HEADER_SEARCH_PATHS: $(PROJECT_DIR)/../SDL2/include
LIBRARY_SEARCH_PATHS:
- $(inherited)
- $(PROJECT_DIR)/../target
- $(PROJECT_DIR)/sdl_build/Debug-iphoneos # This example is building debug
dependencies:
- sdk: Metal.framework
- framework: Libs/libSDL2.a
embed: false
- framework: libios-rust-sdl2.a # <-- Update to "lib[YOUR_CRATE_NAME].lib
embed: false
- sdk: CoreServices.framework
- sdk: CoreMotion.framework
- sdk: CoreGraphics.framework
- sdk: AudioToolbox.framework
- sdk: CoreAudio.framework
- sdk: QuartzCore.framework
- sdk: GameController.framework
- sdk: Foundation.framework
- sdk: OpenGLES.framework
- sdk: UIKit.framework
- sdk: AVFoundation.framework
- sdk: ImageIO.framework
- sdk: Security.framework
- sdk: CoreHaptics.framework
preBuildScripts:
- name: Build Rust
#path: ../build_rush.sh <-- alternative to inlining the script here
# THIS ASSUMES THE PROJECT STRUCTURE AS EXPLAINED ABOVE. Update "ios-rust-sdl2" to match [YOUR_CRATE_NAME]
script: |
cd ${SRCROOT}/..
cargo build --package ios-rust-sdl2 --target aarch64-apple-ios
cp ${SRCROOT}/../target/aarch64-apple-ios/debug/libios-rust-sdl2.a ${SRCROOT}/../target/libios-rust-sdl2.a
xcodebuild -project ${SRCROOT}/../SDL2/Xcode/SDL/SDL.xcodeproj -scheme "Static Library-iOS" build SYMROOT="${SRCROOT}/sdl_build"
Add a src folder in the xcode folder (so [root]/src/main.cpp)
#include "SDL2/SDL.h"
#include <stdio.h>
extern "C" void run_the_game();
extern "C" int main(int argc, char *argv[])
{
run_the_game();
printf("run_the_game returned");
return 0;
}
Assuming you already did brew install xcodegen
or otherwise have xcodegen on your path:
cd xcode
xcodegen
This will produce a .xcodeproj that you can double click to open.
Since a shader is necessary to do anything interesting with rafx, we will need to support bundling asset data with the app. There are two approaches:
Generate the pack file like this, and place it in the root of your crate
run --bin cli -- pack out.pack
# Read comments in project.yml above
# but it's something like this
targets:
RustSdl2App:
sources:
- src
- path: ../out.pack
type: data
buildPhase: resources
Accessing a file on iOS is unfortunately not as straightforward as a posix call. Here’s a snippet you can use. This will give you a path which you can use with standard file access APIs in rust
pub fn find_path_to_bundle_resource(file_name: &str, file_extension: &str) -> Option<String> {
use objc::runtime::{Class, Object};
use objc_id::Id;
use objc_foundation::{INSString, NSString};
use cocoa_foundation::base::nil;
use objc::msg_send;
use objc::sel;
use objc::sel_impl;
unsafe {
let ns_bundle_class = Class::get("NSBundle").unwrap();
let bundle: *mut Object = msg_send![ns_bundle_class, mainBundle];
let path_for_resource = NSString::from_str(file_name).share();
let resource_type = NSString::from_str(file_extension).share();
let path: *mut Object = msg_send![bundle, pathForResource:path_for_resource ofType:resource_type];
let cstr: *const libc::c_char = msg_send![path, UTF8String];
if cstr != std::ptr::null() {
let rstr = std::ffi::CStr::from_ptr(cstr).to_string_lossy().into_owned();
return Some(rstr);
}
return None;
}
}
Use it like this:
let packfile_path = find_path_to_bundle_resource("out", "pack");
let mut asset_resource = {
let packfile = std::fs::File::open(packfile_path).unwrap();
let packfile_loader = rafx::distill::loader::PackfileReader::new(packfile).unwrap();
let loader = Loader::new(Box::new(packfile_loader));
let resolver = Box::new(DefaultIndirectionResolver);
AssetResource::new(loader, resolver)
};
Run this on your PC:
run --bin cli -- host-daemon
Set up distill to stream assets over the network like this:
let mut asset_resource = {
let connect_string = "192.168.0.X:9999"; // This should be your asset daemon host
let rpc_loader = RpcIO::new(connect_string.to_string()).unwrap();
let loader = Loader::new(Box::new(rpc_loader));
let resolver = Box::new(DefaultIndirectionResolver);
AssetResource::new(loader, resolver)
};