Skip to content

Instantly share code, notes, and snippets.

@TheNathannator
Last active January 31, 2025 14:39
Show Gist options
  • Save TheNathannator/9350a183f147c93cd0846328e320ddaf to your computer and use it in GitHub Desktop.
Save TheNathannator/9350a183f147c93cd0846328e320ddaf to your computer and use it in GitHub Desktop.
Unity editor script for extracting the Wasmtime NuGet package's runtime binaries when using NugetForUnity, because NugetForUnity doesn't handle them for you.
(module
(import "host" "print_integer" (func $print_integer (param i32)))
(memory 1)
(global $frame_count (mut i32) (i32.const 0))
(export "frame_count" (global $frame_count))
(func (export "on_update")
(i32.add (global.get $frame_count) (i32.const 1))
global.set $frame_count
global.get $frame_count
call $print_integer
)
)
using UnityEngine;
using System.IO;
using Wasmtime;
using UnityEngine.Assertions;
using System;
namespace Playground
{
public class WasmTest : MonoBehaviour
{
private Engine _wasmEngine;
private Linker _wasmLinker;
private Store _wasmStore;
private Module _wasmModule;
private Instance _wasmInstance;
private int _frameCount = 0;
private Action _wasmOnUpdate;
private Global.Accessor<int> _wasmFrameCount;
private void Awake()
{
_wasmEngine = new Engine();
_wasmLinker = new Linker(_wasmEngine);
_wasmStore = new Store(_wasmEngine);
_wasmLinker.DefineFunction(
"native",
"print_integer",
(int value) => Debug.Log($"From WASM: {value}")
);
string scriptPath = Path.Join(Application.dataPath, "Scripts", "wasm_test.wast");
_wasmModule = Module.FromTextFile(_wasmEngine, scriptPath);
_wasmInstance = _wasmLinker.Instantiate(_wasmStore, _wasmModule);
_wasmOnUpdate = _wasmInstance.GetAction("on_update");
_wasmFrameCount = _wasmInstance.GetGlobal("frame_count").Wrap<int>();
}
private void OnDestroy()
{
// _wasmInstance.Dispose();
_wasmModule.Dispose();
_wasmStore.Dispose();
_wasmLinker.Dispose();
_wasmEngine.Dispose();
}
private void Update()
{
_frameCount++;
_wasmOnUpdate();
var frameCount = _wasmFrameCount.GetValue();
Assert.AreEqual(_frameCount, frameCount);
}
}
}
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Playground.Editor
{
/// <summary>
/// Manually extracts the <c>runtimes</c> folder for Wasmtime,
/// since NugetForUnity doesn't do it for you.
/// </summary>
public class WasmtimeExtractor : AssetPostprocessor
{
private const string ASSET_LABEL = "Wasmtime";
// This directory should be added to your source control ignore file.
private const string OUTPUT_DIRECTORY = "Plugins/Wasmtime-runtime";
static WasmtimeExtractor()
{
Extract(force: false);
}
[MenuItem("Playground/Re-extract Wasmtime", false, 0)]
public static void ReExtract() => Extract(force: true);
private static void Extract(bool force)
{
// Because the version number is part of the folder name for packages,
// we need to check each folder by hand to find the right one
string allPackagesFolder = Path.Join(Application.dataPath, "Packages");
string packageFolder = Directory.EnumerateDirectories(allPackagesFolder, "*", SearchOption.TopDirectoryOnly)
.FirstOrDefault((pkg) => Path.GetFileName(pkg.TrimEnd('/', '\\')).StartsWith("Wasmtime"));
if (packageFolder == null)
{
Debug.LogError("Could not find Wasmtime package folder!");
return;
}
string packageName = Path.GetFileName(packageFolder.TrimEnd('/', '\\'));
string packagePath = Path.Join(packageFolder, packageName + ".nupkg");
string outputFolder = Path.Join(Application.dataPath, OUTPUT_DIRECTORY);
if (!Directory.Exists(outputFolder))
Directory.CreateDirectory(outputFolder);
string versionFile = Path.Join(outputFolder, "version.txt");
if (File.Exists(versionFile))
{
string version = File.ReadAllText(versionFile);
if (!force && version == packageName)
return;
}
using var packageFile = ZipFile.OpenRead(packagePath);
foreach (var entry in packageFile.Entries)
{
// The `runtimes` folder is structured like this:
// runtimes/win-x64/native/wasmtime.dll
string[] components = entry.FullName.Split('/');
if (components.Length < 4 || components[0] != "runtimes")
continue;
string runtime = components[1];
// string "native" = components[2];
string file = components[3];
// Linux ARM64 is not supported by Unity, and trying to delete during the
// post-process stage results in errors in the console
if (runtime == "linux-arm64")
continue;
string outputPath = Path.Combine(outputFolder, runtime, file);
string outputDir = Path.GetDirectoryName(outputPath);
if (!Directory.Exists(outputDir))
Directory.CreateDirectory(outputDir);
entry.ExtractToFile(outputPath, overwrite: true);
string importPath = Path.Combine("Assets", OUTPUT_DIRECTORY, runtime, file);
AssetDatabase.ImportAsset(importPath);
}
File.WriteAllText(versionFile, packageName);
}
// Custom post-processing to automatically apply the right labels
private static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths,
bool didDomainReload
)
{
try
{
AssetDatabase.StartAssetEditing();
foreach (string asset in importedAssets)
{
string[] components = asset.Split('/', '\\');
if (components.Length < 5 ||
components[0] != "Assets" ||
components[1] != "Plugins" ||
components[2] != "Wasmtime-runtime")
{
continue;
}
string runtime = components[3];
// string file = components[4];
var plugin = AssetImporter.GetAtPath(asset) as PluginImporter;
if (plugin == null)
continue;
SetPluginTargetProperties(plugin, runtime);
AssetDatabase.SetLabels(plugin, new[] { ASSET_LABEL });
}
}
catch (Exception ex)
{
Debug.LogError($"Error while importing Wasmtime runtime assets!");
Debug.LogException(ex);
}
finally
{
AssetDatabase.StopAssetEditing();
}
}
private static void SetPluginTargetProperties(PluginImporter plugin, string runtime)
{
void SetProperties(PluginImporter plugin, BuildTarget target, string editorPlatform, string cpu)
{
plugin.SetCompatibleWithPlatform(target, true);
plugin.SetPlatformData(target, "CPU", cpu);
plugin.SetCompatibleWithEditor(true);
plugin.SetEditorData("OS", editorPlatform);
plugin.SetEditorData("CPU", cpu);
}
plugin.SetCompatibleWithAnyPlatform(false);
switch (runtime)
{
case "win-x86":
case "win7-x86":
case "win10-x86":
SetProperties(plugin, BuildTarget.StandaloneWindows, "Windows", "AnyCPU");
break;
case "win-x64":
case "win7-x64":
case "win10-x64":
SetProperties(plugin, BuildTarget.StandaloneWindows64, "Windows", "x86_64");
break;
case "osx-x64":
SetProperties(plugin, BuildTarget.StandaloneOSX, "OSX", "x86_64");
break;
case "osx-arm64":
SetProperties(plugin, BuildTarget.StandaloneOSX, "OSX", "ARM64");
break;
case "linux-x64":
SetProperties(plugin, BuildTarget.StandaloneLinux64, "Linux", "x86_64");
break;
case "linux-arm64":
// Not supported by Unity
break;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment