Skip to content

Commit

Permalink
fix: 🐛 loading wasm binaries (#34)
Browse files Browse the repository at this point in the history
* fix: 🐛 loading wasm binaries

* fix: 🐛 types

* chore: 🤖 add changeset

* chore: 🤖 update README
  • Loading branch information
theashraf authored Nov 9, 2023
1 parent 4132ac2 commit 3388c48
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 112 deletions.
6 changes: 6 additions & 0 deletions .changeset/sweet-comics-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@lottiefiles/dotlottie-web': minor
---

fix: 🐛 loading wasm binaries
feat:🎸 add setWasmUrl static method
4 changes: 4 additions & 0 deletions apps/dotlottie-web-example/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import './styles.css';
import { DotLottie } from '@lottiefiles/dotlottie-web';

import wasmUrl from '../../../packages/web/dist/renderer.wasm?url';

const app = document.getElementById('app') as HTMLDivElement;

app.innerHTML = `
Expand All @@ -23,6 +25,8 @@ app.innerHTML = `
</div>
`;

DotLottie.setWasmUrl(wasmUrl);

fetch('/hamster.lottie')
.then(async (res) => res.arrayBuffer())
.then((data): void => {
Expand Down
7 changes: 7 additions & 0 deletions packages/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* [Options](#options)
* [Properties](#properties)
* [Methods](#methods)
* [Static Methods](#static-methods)
* [Events](#events)
* [Development](#development)
* [Setup](#setup)
Expand Down Expand Up @@ -142,6 +143,12 @@ const dotLottie = new DotLottie({
| `addEventListener(event: string, listener: Function)` | Registers a function to respond to a specific animation event. |
| `removeEventListener(event: string, listener?: Function)` | Removes a previously registered function from responding to a specific animation event. |

### Static Methods

| Method | Description |
| ------------------------- | ----------------------------------------- |
| `setWasmUrl(url: string)` | Sets the URL to the renderer.wasm binary. |

### Events

| Event | Description |
Expand Down
27 changes: 13 additions & 14 deletions packages/web/src/dotlottie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import type { EventListener, EventType } from './event-manager';
import { EventManager } from './event-manager';
import type { Renderer } from './renderer-wasm';
import { createRenderer } from './renderer-wasm';
import { WasmLoader } from './renderer-wasm';
import { getAnimationJSONFromDotLottie, loadAnimationJSONFromURL } from './utils';

const MS_TO_SEC_FACTOR = 1000;
Expand Down Expand Up @@ -79,8 +79,10 @@ export class DotLottie {
this._speed = config.speed ?? 1;
this._autoplay = config.autoplay ?? false;

this._initRenderer()
.then(() => {
WasmLoader.awaitInstance()
.then((renderer) => {
this._renderer = renderer;

if (config.src) {
this._loadAnimationFromURL(config.src);
} else if (config.data) {
Expand Down Expand Up @@ -160,17 +162,6 @@ export class DotLottie {
}
// #endregion

// #region Private Methods
/**
* Initializes the renderer.
*
* @returns A promise that resolves when the renderer is initialized.
*/
private async _initRenderer(): Promise<void> {
if (this._renderer) return;
this._renderer = await createRenderer();
}

/**
* Loads and initializes the animation from a given URL.
*
Expand Down Expand Up @@ -433,5 +424,13 @@ export class DotLottie {
this._eventManager.removeEventListener(type, listener);
}

/**
* Sets the source URL of the WASM file to load.
* @param url - The URL of the WASM file to load.
*/
public static setWasmUrl(url: string): void {
WasmLoader.setWasmUrl(url);
}

// #endregion
}
1 change: 1 addition & 0 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
export * from './dotlottie';
export * from './event-manager';
export * from './utils';
export * from './renderer-wasm';
135 changes: 43 additions & 92 deletions packages/web/src/renderer-wasm/bin/renderer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var createRendererModule = (() => {
var _scriptDir = import.meta.url;
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;

return function (moduleArg = {}) {
var Module = moduleArg;
Expand Down Expand Up @@ -238,13 +238,10 @@ var createRendererModule = (() => {

var wasmBinaryFile;

if (Module['locateFile']) {
wasmBinaryFile = 'renderer.wasm';
if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}
} else {
wasmBinaryFile = new URL('renderer.wasm', import.meta.url).href;
wasmBinaryFile = 'renderer.wasm';

if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}

function getBinarySync(file) {
Expand Down Expand Up @@ -1360,26 +1357,6 @@ var createRendererModule = (() => {
}
};

function newFunc(constructor, argumentList) {
if (!(constructor instanceof Function)) {
throw new TypeError(`new_ called with constructor type ${typeof constructor} which is not a function`);
}
/*
* Previously, the following line was just:
* function dummy() {};
* Unfortunately, Chrome was preserving 'dummy' as the object's name, even
* though at creation, the 'dummy' has the correct constructor name. Thus,
* objects created with IMVU.new would show up in the debugger as 'dummy',
* which isn't very helpful. Using IMVU.createNamedFunction addresses the
* issue. Doublely-unfortunately, there's no way to write a test for this
* behavior. -NRD 2013.02.22
*/ var dummy = createNamedFunction(constructor.name || 'unknownFunctionName', function () {});
dummy.prototype = constructor.prototype;
var obj = new dummy();
var r = constructor.apply(obj, argumentList);
return r instanceof Object ? r : obj;
}

function craftInvokerFunction(
humanName,
argTypes,
Expand All @@ -1401,72 +1378,46 @@ var createRendererModule = (() => {
}
}
var returns = argTypes[0].name !== 'void';
var argsList = '';
var argsListWired = '';
for (var i = 0; i < argCount - 2; ++i) {
argsList += (i !== 0 ? ', ' : '') + 'arg' + i;
argsListWired += (i !== 0 ? ', ' : '') + 'arg' + i + 'Wired';
}
var invokerFnBody = `\n return function ${makeLegalFunctionName(
humanName,
)}(${argsList}) {\n if (arguments.length !== ${
argCount - 2
}) {\n throwBindingError('function ${humanName} called with ' + arguments.length + ' arguments, expected ${
argCount - 2
}');\n }`;
if (needsDestructorStack) {
invokerFnBody += 'var destructors = [];\n';
}
var dtorStack = needsDestructorStack ? 'destructors' : 'null';
var args1 = ['throwBindingError', 'invoker', 'fn', 'runDestructors', 'retType', 'classParam'];
var args2 = [throwBindingError, cppInvokerFunc, cppTargetFunc, runDestructors, argTypes[0], argTypes[1]];
if (isClassMethodFunc) {
invokerFnBody += 'var thisWired = classParam.toWireType(' + dtorStack + ', this);\n';
}
for (var i = 0; i < argCount - 2; ++i) {
invokerFnBody +=
'var arg' +
i +
'Wired = argType' +
i +
'.toWireType(' +
dtorStack +
', arg' +
i +
'); // ' +
argTypes[i + 2].name +
'\n';
args1.push('argType' + i);
args2.push(argTypes[i + 2]);
}
if (isClassMethodFunc) {
argsListWired = 'thisWired' + (argsListWired.length > 0 ? ', ' : '') + argsListWired;
}
invokerFnBody +=
(returns || isAsync ? 'var rv = ' : '') +
'invoker(fn' +
(argsListWired.length > 0 ? ', ' : '') +
argsListWired +
');\n';
if (needsDestructorStack) {
invokerFnBody += 'runDestructors(destructors);\n';
} else {
for (var i = isClassMethodFunc ? 1 : 2; i < argTypes.length; ++i) {
var paramName = i === 1 ? 'thisWired' : 'arg' + (i - 2) + 'Wired';
if (argTypes[i].destructorFunction !== null) {
invokerFnBody += paramName + '_dtor(' + paramName + '); // ' + argTypes[i].name + '\n';
args1.push(paramName + '_dtor');
args2.push(argTypes[i].destructorFunction);
var expectedArgCount = argCount - 2;
var argsWired = new Array(expectedArgCount);
var invokerFuncArgs = [];
var destructors = [];
return function () {
if (arguments.length !== expectedArgCount) {
throwBindingError(
`function ${humanName} called with ${arguments.length} arguments, expected ${expectedArgCount}`,
);
}
destructors.length = 0;
var thisWired;
invokerFuncArgs.length = isClassMethodFunc ? 2 : 1;
invokerFuncArgs[0] = cppTargetFunc;
if (isClassMethodFunc) {
thisWired = argTypes[1]['toWireType'](destructors, this);
invokerFuncArgs[1] = thisWired;
}
for (var i = 0; i < expectedArgCount; ++i) {
argsWired[i] = argTypes[i + 2]['toWireType'](destructors, arguments[i]);
invokerFuncArgs.push(argsWired[i]);
}
var rv = cppInvokerFunc.apply(null, invokerFuncArgs);
function onDone(rv) {
if (needsDestructorStack) {
runDestructors(destructors);
} else {
for (var i = isClassMethodFunc ? 1 : 2; i < argTypes.length; i++) {
var param = i === 1 ? thisWired : argsWired[i - 2];
if (argTypes[i].destructorFunction !== null) {
argTypes[i].destructorFunction(param);
}
}
}
if (returns) {
return argTypes[0]['fromWireType'](rv);
}
}
}
if (returns) {
invokerFnBody += 'var ret = retType.fromWireType(rv);\n' + 'return ret;\n';
} else {
}
invokerFnBody += '}\n';
args1.push(invokerFnBody);
return newFunc(Function, args1).apply(null, args2);
return onDone(rv);
};
}

var __embind_register_class_constructor = (
Expand Down
64 changes: 60 additions & 4 deletions packages/web/src/renderer-wasm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* Copyright 2023 Design Barn Inc.
*/

/* eslint-disable no-negated-condition */
/* eslint-disable no-console */

import pkg from '../../package.json';

import createRendererModule from './bin/renderer';

export interface Renderer {
Expand All @@ -16,10 +21,61 @@ export interface Renderer {
update(): boolean;
}

export async function createRenderer(): Promise<Renderer> {
const rendererModule = await createRendererModule();
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class WasmLoader {
private static _renderer: Renderer | null = null;

private static _isLoading = false;

private static _wasmURL = `https://unpkg.com/${pkg.name}@${pkg.version}/dist/renderer.wasm`;

private constructor() {
// Class is never instantiated
}

public static loadRenderer(): void {
createRendererModule({
locateFile: () => WasmLoader._wasmURL,
})
.then((module: { Renderer: new () => Renderer }) => {

Check warning on line 40 in packages/web/src/renderer-wasm/index.ts

View workflow job for this annotation

GitHub Actions / validate

Prefer await to then()/catch()/finally()
WasmLoader._renderer = new module.Renderer();
})
.catch(() => {

Check warning on line 43 in packages/web/src/renderer-wasm/index.ts

View workflow job for this annotation

GitHub Actions / validate

Prefer await to then()/catch()/finally()
const backupJsdelivrUrl = `https://cdn.jsdelivr.net/npm/${pkg.name}@${pkg.version}/dist/renderer.wasm`;

if (WasmLoader._wasmURL.toLowerCase() !== backupJsdelivrUrl) {
console.warn(`Failed to load WASM from ${WasmLoader._wasmURL}, trying jsdelivr as a backup`);
WasmLoader.setWasmUrl(backupJsdelivrUrl);
WasmLoader.loadRenderer();
} else {
console.error(
`Could not load Rive WASM file from unpkg or jsdelivr, network connection may be down, or \
you may need to call set a new WASM source via WasmLoader.setWasmUrl() and call \
WasmLoader.loadRenderer() again`,
);
}
});
}

public static getInstance(callback: (renderer: Renderer) => void): void {
if (!WasmLoader._isLoading) {
WasmLoader._isLoading = true;
WasmLoader.loadRenderer();
}

if (WasmLoader._renderer) {
// eslint-disable-next-line node/callback-return
callback(WasmLoader._renderer);
} else {
setTimeout(() => WasmLoader.getInstance(callback), 100);
}
}

const renderer = new rendererModule.Renderer();
public static async awaitInstance(): Promise<Renderer> {
return new Promise<Renderer>((resolve) => WasmLoader.getInstance((renderer: Renderer): void => resolve(renderer)));
}

return renderer;
public static setWasmUrl(url: string): void {
WasmLoader._wasmURL = url;
}
}
2 changes: 1 addition & 1 deletion packages/web/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"outDir": "./dist",

// Source root directory
"rootDir": "./src"
"rootDir": "."
},

// Files included in compilation
Expand Down
2 changes: 1 addition & 1 deletion packages/web/wasm_x86_i686.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exe_suffix = 'js'

[built-in options]
cpp_args = ['-Wshift-negative-value', '-flto', '-Os', '-fno-exceptions', '-ffunction-sections', '-fdata-sections']
cpp_link_args = ['-Wshift-negative-value', '-flto', '-Os', '--bind', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sFORCE_FILESYSTEM=0', '-sMODULARIZE=1', '-sEXPORT_NAME=createRendererModule', '-sEXPORT_ES6=1', '-sUSE_ES6_IMPORT_META=1', '-sENVIRONMENT=web', '-sFILESYSTEM=0', '--no-entry', '--strip-all', '--minify=0']
cpp_link_args = ['-Wshift-negative-value', '-flto', '-Os', '--bind', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sFORCE_FILESYSTEM=0', '-sMODULARIZE=1', '-sEXPORT_NAME=createRendererModule', '-sEXPORT_ES6=1', '-sUSE_ES6_IMPORT_META=0', '-sENVIRONMENT=web', '-sFILESYSTEM=0', '--no-entry', '--strip-all', '--minify=0', '-sDYNAMIC_EXECUTION=0']

[host_machine]
system = 'emscripten'
Expand Down

0 comments on commit 3388c48

Please sign in to comment.