--- title: New animation --- # New animation system proposal As veloren increases in size and complexity the need arises for a new animation system ## Current problems The solution that is being used currently relies on coding directly into `voxygen` this has multiple disadvantages: - The animation code increases CI by a large amount - No hot reloading - Use of external libs like the `keyframe` crate causes more CI stress and as such is avoided - Poor man slipped needs an editor instead of doing everything by hand and this aproach makes is nearly impossible ## Current implementation As it was said earlier the animations are coded directly `voxygen` under the `anim` module. Each skeleton has further a submodule with all it's associated animations. The animations themselves are made by implementing the `Animation` trait. The trait has a two associated the skeleton and dependencies of the system and a single method `update_skeleton` with the signature ```rust ( skeleton: &Self::Skeleton, dependency: Self::Dependency, anim_time: f64, rate: &mut f32, skeleton_attr: &<<Self as Animation>::Skeleton as Skeleton>::Attr, ) -> Self::Skeleton ``` This method gets called in `scene/figure/mod.rs` during the `maintain` call. Despite the associated types the call is strongly typed and the dependency type is handled manually and not by the specs systems. This is important because it makes the transition easier. The skeletons are made implementing the `Skeleton` trait. ## Solutions This proposal describes two aproaches and their advantages and disadvantages ### Animation format files The first proposed solution uses a file with a to be decided animation format, the format could be custom or a open standard like `collada`. The use of animation file brings many benefits like hot-reloading out of the box, many are supported by editors like `blender` and as such the development might become easier and are easily plugglable in the current system, but they do present some disadvatages for example `collada` also encodes information about model and illumination and the schema that is used is very combersome and wastes spacebut perhaps most importantly it loses the ability to define animations trough that makes it less powerfull and the process of porting might be harder or in some cases require some hacks. This could be solved by creating a textual format that describes animations as small programs using a custom micro language this has the problem of not allowing the kind of optimizations that doing it directly on the code had and it's still less powerfull and compared to the next solution unnecessary. Also this system might not be the best to include when modding as it limits the creativity and forces tools upon the modder. ### WebAssembly The second proposed solution is too keep doing animations in code but on a separate crate that's compiled to webassembly and then ran in a runtime during game. This might seem like it adds unnecessary complexity and a whole wasm runtime to the game but the modding was already decided to be done in wasm so it doesn't bring anything of new. During research two runtimes seemed to be the most usable [`wasmer`](https://wasmer.io/) and [`wasmtime`](https://wasmtime.dev/) both can run the current core spec of wasm and are both supported by commercial use but the `wasmtime` has more benefits to our usecase first it's the parent of `cranelift` the compiler of both engines so any performance issue with it's better resolved and most importantly they include recent wasm proposals like `Anyref` and the interface types(Although this one had to be taken down temporarily while a better solution is worked, see: https://github.com/bytecodealliance/wasmtime/issues/677). The solution would be to split the animations in two crates `animation-common` and `animations` the first one would have all the types like skeletons, bones and such and would be included in both the `animations` crate and in `voxygen` it would serve as a connection between both the reason `common` isn't used instead is to reduce both compile times and sizes of the `animations` crate. The `animations` would contain all the logic of the animations and would be compiled into wasm. The `animations` would be "required" to expose a single function `metadata` this function would return a struct `Metadata` defined as: ```rust type Animation = (String, String); #[repr(C)] struct Metadata { version: u32, skeletons: Vec<(Skeleton, Vec<Animation>)> } #[repr(C)] enum Skeleton { ... } ``` The version would be checked so that the `animation-common` crate is the same as some transmutes from memory will be required. The Animations are a string that correspond with the name of the function that the runtime will call when dealing with that specific animation ```rust #[no_mangle] extern "C" fn metadata() -> Metadata { Metadata { version: 0, // Should be filled by the common crate skeletons: vec![(Skeleton::Character,vec!["run","character_run_anim"])] } } #[no_mangle] extern "C" fn character_run_anim( skel: CharacterSkeleton, global_time: f32, ... ) -> CharacterSkeleton { ... } ``` All types and functions are needed to be defined as `#[repr(C)]` and `extern "C"` so that modding can be supported and because when we call a wasm function we are passing a FFI boundary. Currently this solution has one problem: Evertime we pass data we need to do a memory copy because of the wasm sandboxing and that's the reason we need to transmute but also because why we should chose `wasmtime` because using `Anyref` and the interface types we can avoid it but that is beyond the scope of a potential first implementation because it requires configuring a appropriate toolchain and waiting for the respective proposals to be stabilized in `wasmtime` Demonstration: https://github.com/JCapucho/anim-demo The example uses the same code as in the idle characther animation after building both the runtime and the animation crate with release (furthermore the animation was built with `lto` and `opt-level = "s"` to reduce size) the runtime is set to optimize for both speed and size. Running this we get that the instantiation took `0.0858`seconds, this would only be needed at start and reload, the metadata fetch took `0.0137656`seconds and the skeleton update took `0.0000702`seconds. The test was conducted on a i7-7700HQ @ 2.80 GHz over a single run. The code isn't optimized and doesn't use the most recent proposals, everything is encoded with `bincode` so optimizations like updating the skeleton directly in the staging buffer and returning a multi value instead of a the `AnimReturn` type could provide further speed improvements. Most of the time in the get metadata was spent in the `Vec` allocation so it can also be improved. The use of `Anyref` and the interface types could make copy-less updates possible but the benefits aren't enough to plug an unstabilized proposal yet. ##### Update After using raw pointers and don't logging the metadata to the terminal I was able to push the update skeleton time to`0.000019`seconds and the metadata fetch to `0.0015903` seconds