Skip to content

Commit

Permalink
Add support for packed animation plugins (wasm only)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrozycki committed Dec 1, 2024
1 parent f28e0c6 commit 5d0758e
Show file tree
Hide file tree
Showing 18 changed files with 520 additions and 118 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/target
**/captures
*.crab

*.csv
*.log
Expand All @@ -14,4 +15,4 @@ webui/dist
*-wal

.DS_Store
.idea
.idea
58 changes: 52 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"visualizer",
"events",
"test-wasm-animation",
"animation-packer",
]

resolver = "2"
15 changes: 15 additions & 0 deletions animation-packer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "animation-packer"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133"
tar = "0.4.43"
thiserror = "2.0.3"

[features]
default = ["pack", "unpack"]
pack = []
unpack = []
14 changes: 14 additions & 0 deletions animation-packer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Rustmas Animation Packer
========================

This crate contains utilities for wrapping animations into Compiled Rustmas
Animation Bundles (yes, we worked very hard on this acronym, glad you noticed).

Additionally, an executable `crabwrap` is provided to help create animation
plugin files from animation code. In order to create an animation plugin file,
run `crabwrap` from the root directory of your animation. Before that,
you might need to install the `wasm32-wasip2` target for your Rust toolchain:

```
rustup target add wasm32-wasip2
```
95 changes: 95 additions & 0 deletions animation-packer/src/bin/crabwrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::{
fs::read_dir,
path::PathBuf,
process::{Command, ExitStatus},
};

fn build_plugin() -> std::io::Result<ExitStatus> {
let mut cmd = Command::new("cargo")
.args(["build", "--target", "wasm32-wasip2", "--release"])
.spawn()?;
cmd.wait()
}

fn find_animation_id() -> std::io::Result<String> {
std::env::current_dir()?
.components()
.last()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "Invalid project path"))
.map(|c| c.as_os_str().to_string_lossy().to_string())
.map(|s| s.replace('-', "_"))
}

fn find_manifest() -> std::io::Result<PathBuf> {
let manifest_path = std::env::current_dir()?.join("manifest.json");

if !manifest_path.exists() {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not find manifest.json",
))
} else {
Ok(manifest_path)
}
}

fn find_animation_executable() -> std::io::Result<PathBuf> {
let project_root = find_project_root()?;
let animation_id = find_animation_id()?;

let executable_path = project_root
.join("target/wasm32-wasip2/release")
.join(format!("{animation_id}.wasm"));

if !executable_path.exists() {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not find animation executable",
))
} else {
Ok(executable_path)
}
}

fn get_output_path() -> std::io::Result<PathBuf> {
let animation_id = find_animation_id()?;
Ok(std::env::current_dir()?
.as_path()
.join(format!("{animation_id}.crab")))
}

fn find_project_root() -> std::io::Result<PathBuf> {
std::env::current_dir()?
.as_path()
.ancestors()
.find(|p| {
let Ok(dir) = read_dir(p) else {
return false;
};
dir.into_iter()
.filter_map(|p| p.ok())
.map(|p| p.file_name().to_string_lossy().to_string())
.any(|p| p == "Cargo.lock")
})
.map(PathBuf::from)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not find Cargo.lock until filesystem root",
)
})
}

fn main() {
let manifest_path = find_manifest().expect("Could not find manifest.json file");

build_plugin().expect("Failed to build the plugin");

let executable_path = find_animation_executable().expect("Could not find animation executable");
let output_path = get_output_path().expect("Could not generate output path");

animation_packer::pack::pack_plugin(&output_path, &executable_path, &manifest_path)
.expect("Failed to pack the plugin");

println!("Plugin ready at {}", output_path.to_string_lossy());
}
108 changes: 108 additions & 0 deletions animation-packer/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::{
path::{Path, PathBuf},
process::{Command, Stdio},
};

use serde::Deserialize;

use crate::unpack::PluginUnpackError;

#[derive(Debug, thiserror::Error)]
pub enum PluginConfigError {
#[error("Failed to parse manifest: {reason}")]
InvalidManifest { reason: String },

#[error("Failed to unpack plugin")]
InvalidPluginPack(#[from] PluginUnpackError),

#[error("Directory containing plugin has non UTF-8 name")]
NonUtf8DirectoryName,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginType {
#[default]
Native,
Wasm,
}

#[derive(Debug, Clone, Deserialize)]
pub struct PluginManifest {
display_name: String,
#[serde(default)]
plugin_type: PluginType,
}

#[derive(Debug, Clone)]
pub struct PluginConfig {
pub(crate) animation_id: String,
pub(crate) manifest: PluginManifest,
pub(crate) path: PathBuf,
}

impl PluginConfig {
pub fn new(path: PathBuf) -> Result<Self, PluginConfigError> {
let manifest: PluginManifest =
serde_json::from_slice(&std::fs::read(path.join("manifest.json")).map_err(|e| {
PluginConfigError::InvalidManifest {
reason: format!("IO error: {}", e),
}
})?)
.map_err(|e| PluginConfigError::InvalidManifest {
reason: e.to_string(),
})?;

let animation_id = path
.file_name()
.unwrap()
.to_str()
.ok_or(PluginConfigError::NonUtf8DirectoryName)?
.to_owned();

Ok(Self {
animation_id,
manifest,
path,
})
}

pub fn animation_id(&self) -> &str {
&self.animation_id
}

pub fn animation_name(&self) -> &str {
&self.manifest.display_name
}

pub fn plugin_type(&self) -> PluginType {
self.manifest.plugin_type
}

pub fn path(&self) -> &Path {
self.path.as_path()
}

pub fn executable_path(&self) -> PathBuf {
let executable_name = if self.manifest.plugin_type == PluginType::Wasm {
"plugin.wasm"
} else if cfg!(windows) {
"plugin.exe"
} else {
"plugin"
};

self.path.join(executable_name)
}

pub fn is_executable(&self) -> bool {
match self.manifest.plugin_type {
PluginType::Native => Command::new(self.executable_path())
.stdout(Stdio::null())
.stdin(Stdio::null())
.spawn()
.is_ok(),
PluginType::Wasm => self.executable_path().exists(),
}
}
}
7 changes: 7 additions & 0 deletions animation-packer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod config;

#[cfg(feature = "pack")]
pub mod pack;

#[cfg(feature = "unpack")]
pub mod unpack;
Loading

0 comments on commit 5d0758e

Please sign in to comment.