diff --git a/.rive_head b/.rive_head index fc477217..75c966c8 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -b098ad23a15cc7b11f144e407374c83d5db0fee0 +ebff4893cb0698855530d530a6f952880dceb229 diff --git a/js/examples/_frameworks/parcel_example_canvas/index.js b/js/examples/_frameworks/parcel_example_canvas/index.js index 792657b8..549df32c 100644 --- a/js/examples/_frameworks/parcel_example_canvas/index.js +++ b/js/examples/_frameworks/parcel_example_canvas/index.js @@ -1,6 +1,6 @@ import "regenerator-runtime"; -// import { Rive, Fit, Alignment, Layout, EventType } from "@rive-app/canvas"; -import { Rive, Fit, Alignment, Layout, EventType } from "@rive-app/canvas-lite"; +import { Rive, Fit, Alignment, Layout, EventType } from "@rive-app/canvas"; +// import { Rive, Fit, Alignment, Layout, EventType } from "@rive-app/canvas-lite"; //import { Rive, Fit, Alignment, Layout } from "@rive-app/webgl"; import AvatarAnimation from "./look.riv"; import TapeMeshAnimation from "./tape.riv"; diff --git a/js/src/rive_advanced.mjs.d.ts b/js/src/rive_advanced.mjs.d.ts index 3f597932..48171b3a 100644 --- a/js/src/rive_advanced.mjs.d.ts +++ b/js/src/rive_advanced.mjs.d.ts @@ -90,6 +90,15 @@ export interface RiveCanvas { * @param requestID - ID of the requestAnimationFrame request to cancel */ cancelAnimationFrame(requestID: number): void; + /** + * A Rive-specific function to "flush" queued up draw calls from using the renderer. + * + * This should only be invoked once at the end of a loop in a regular JS + * requestAnimationFrame loop, and should not be used with the Rive-wrapped + * requestAnimationFrame (aka, the requestAnimationFrame() API on this object) as that + * API will handle flushing the draw calls implicitly. + */ + resolveAnimationFrame(): void; /** * Debugging tool to showcase the FPS in the corner of the screen in a new div. If a callback * function is provided, this function passes the FPS count to the callback instead of creating a diff --git a/wasm/examples/out_of_band_example/index.ts b/wasm/examples/out_of_band_example/index.ts index 9f345b2b..835ab0b2 100644 --- a/wasm/examples/out_of_band_example/index.ts +++ b/wasm/examples/out_of_band_example/index.ts @@ -65,14 +65,14 @@ async function main() { const cachedImageAsset = (asset) => { let image = imageCache[imageCacheIndex++ % imageCache.length]; asset.setRenderImage(image); - rive.requestAnimationFrame(draw); + requestAnimationFrame(draw); // IMPORTANT: to clear the cache, be sure to also call .unref() on each asset. }; const cachedFontAsset = (asset) => { let font = fontCache[fontCacheIndex++ % fontCache.length]; asset.setFont(font); - rive.requestAnimationFrame(draw); + requestAnimationFrame(draw); // IMPORTANT: to clear the cache, be sure to also call .unref() on each asset. }; @@ -80,7 +80,7 @@ async function main() { fetch("https://picsum.photos/1000/1500").then(async (res) => { rive.decodeImage(new Uint8Array(await res.arrayBuffer()), (image) => { asset.setRenderImage(image); - rive.requestAnimationFrame(draw); + requestAnimationFrame(draw); // IMPORTANT: call unref, so that we do not keep the asset alive with // a reference from javascript. image.unref(); @@ -102,7 +102,7 @@ async function main() { rive.decodeFont(new Uint8Array(await res.arrayBuffer()), (font) => { asset.setFont(font); - rive.requestAnimationFrame(draw); + requestAnimationFrame(draw); // IMPORTANT: call unref, so that we do not keep the asset alive with // a reference from javascript. font.unref(); @@ -221,9 +221,10 @@ async function main() { renderer.flush(); // Draw more if we want to. - rive.requestAnimationFrame(draw); + requestAnimationFrame(draw); + rive.resolveAnimationFrame(); } - rive.requestAnimationFrame(draw); + requestAnimationFrame(draw); } main(); diff --git a/wasm/examples/out_of_band_example/package.json b/wasm/examples/out_of_band_example/package.json index ee1dd9c7..7c676700 100644 --- a/wasm/examples/out_of_band_example/package.json +++ b/wasm/examples/out_of_band_example/package.json @@ -1,5 +1,5 @@ { - "name": "rive_centaur_game", + "name": "out_of_band_example", "private": true, "version": "1.0.0", "description": "", diff --git a/wasm/examples/parcel_example/checkForLeaks.js b/wasm/examples/parcel_example/checkForLeaks.js new file mode 100644 index 00000000..72513d24 --- /dev/null +++ b/wasm/examples/parcel_example/checkForLeaks.js @@ -0,0 +1,78 @@ +export async function checkForLeaks(rive) { + const riveEx = RIVE_EXAMPLES[0]; + const { hasStateMachine } = riveEx; + var stateMachine, animation; + const bytes = await (await fetch(new Request(riveEx.riveFile))).arrayBuffer(); + const file = await rive.load(new Uint8Array(bytes)); + const artboard = file.defaultArtboard(); + artboard.advance(0); + if (hasStateMachine) { + stateMachine = new rive.StateMachineInstance( + artboard.stateMachineByIndex(0), + artboard + ); + } else { + animation = new rive.LinearAnimationInstance( + artboard.animationByName(riveEx.animation), + artboard + ); + } + const num = 0; + let canvas = document.getElementById(`canvas${num}`); + if (!canvas) { + const body = document.querySelector("body"); + canvas = document.createElement("canvas"); + canvas.id = `canvas${num}`; + body.appendChild(canvas); + } + canvas.width = "400"; + canvas.height = "400"; + // Don't use the offscreen renderer for FF as it should have a context limit of 300 + const renderer = rive.makeRenderer(canvas, true); + var elapsedSeconds = 0.0167; + // Render 20 frames. + for (var i = 0; i < 1000; i++) { + renderer.clear(); + if (artboard) { + if (stateMachine) { + stateMachine.advance(elapsedSeconds); + } + if (animation) { + animation.advance(elapsedSeconds); + animation.apply(1); + } + artboard.advance(elapsedSeconds); + renderer.save(); + renderer.align( + rive.Fit.contain, + rive.Alignment.center, + { + minX: 0, + minY: 0, + maxX: canvas.width, + maxY: canvas.height, + }, + artboard.bounds + ); + artboard.draw(renderer); + renderer.restore(); + } + renderer.flush(); + } + + renderer.delete(); + if (stateMachine) { + stateMachine.delete(); + } + if (animation) { + animation.delete(); + } + if (artboard) { + artboard.delete(); + } + file.delete(); + rive.cleanup(); + // Report any leaks. + rive.doLeakCheck(); + console.log("END"); +} diff --git a/wasm/examples/parcel_example/index.html b/wasm/examples/parcel_example/index.html index 4edd5950..9febeced 100644 --- a/wasm/examples/parcel_example/index.html +++ b/wasm/examples/parcel_example/index.html @@ -1,7 +1,7 @@ - + diff --git a/wasm/examples/parcel_example/index.js b/wasm/examples/parcel_example/index.js index 8af4906d..f5b50d98 100644 --- a/wasm/examples/parcel_example/index.js +++ b/wasm/examples/parcel_example/index.js @@ -14,7 +14,8 @@ import RiveCanvas from "../../../js/npm/canvas_advanced_single/canvas_advanced_s // (Release) Canvas Advanced Single Lite // import RiveCanvas from "../../build/canvas_advanced_lite_single/bin/release/canvas_advanced_single.mjs"; -import { registerTouchInteractions } from "../../../js/src/utils"; +// import {checkForLeaks} from "./checkForLeaks"; + import AvatarAnimation from "./look.riv"; import TapeMeshAnimation from "./tape.riv"; import BirdAnimation from "./birb.riv"; @@ -24,7 +25,6 @@ import SwitchAnimation from "./switch_event_example.riv"; import TestText from "./text_test_2.riv"; import "./main.css"; -const randomNum = Math.ceil(Math.random() * 100 * 5) + 100; const RIVE_EXAMPLES = { 0: { riveFile: BallAnimation, @@ -47,7 +47,7 @@ const RIVE_EXAMPLES = { 4: { riveFile: TruckAnimation, hasStateMachine: true, - stateMachine: "", + stateMachine: "drive", }, 5: { riveFile: AvatarAnimation, @@ -60,9 +60,8 @@ const RIVE_EXAMPLES = { }, }; -// Loads a default animation and displays it using the advanced api. Drag and -// drop .riv files to see them and play their default animations. -async function renderRiveAnimation({ rive, num, hasRandomSizes }) { +// Load in the Rive File, retrieve the default artboard, a named state machine, or a named animation +async function retrieveRiveContents({ rive, num }) { async function loadDefault() { const riveEx = RIVE_EXAMPLES[num % Object.keys(RIVE_EXAMPLES).length]; const { hasStateMachine } = riveEx; @@ -73,7 +72,7 @@ async function renderRiveAnimation({ rive, num, hasRandomSizes }) { artboard = file.defaultArtboard(); if (hasStateMachine) { stateMachine = new rive.StateMachineInstance( - artboard.stateMachineByIndex(0), + artboard.stateMachineByName(riveEx.stateMachine), artboard ); } else { @@ -85,167 +84,106 @@ async function renderRiveAnimation({ rive, num, hasRandomSizes }) { } await loadDefault(); - let canvas = document.getElementById(`canvas${num}`); - if (!canvas) { - const body = document.querySelector("body"); - canvas = document.createElement("canvas"); - canvas.id = `canvas${num}`; - body.appendChild(canvas); - } - canvas.width = hasRandomSizes ? `${randomNum}` : "400"; - canvas.height = hasRandomSizes ? `${randomNum}` : "400"; - // Don't use the offscreen renderer for FF as it should have a context limit of 300 - const renderer = rive.makeRenderer(canvas, true); - - // Register cursor mouse actions to trigger Rive touch interactions - const activeStateMachines = stateMachine ? [stateMachine] : []; - registerTouchInteractions({ - canvas, - artboard, - stateMachines: activeStateMachines, - renderer, - rive, - fit: rive.Fit.contain, - alignment: rive.Alignment.center, - }); - - let lastTime = 0; let artboard, stateMachine, animation; - function draw(time) { - if (!lastTime) { - lastTime = time; - } - const elapsedMs = time - lastTime; - const elapsedSeconds = elapsedMs / 1000; - lastTime = time; - - renderer.clear(); - if (artboard) { - if (stateMachine) { - stateMachine.advance(elapsedSeconds); - } - if (animation) { - animation.advance(elapsedSeconds); - animation.apply(1); - } - artboard.advance(elapsedSeconds); - renderer.save(); - renderer.align( - rive.Fit.contain, - rive.Alignment.center, - { - minX: 0, - minY: 0, - maxX: canvas.width, - maxY: canvas.height, - }, - artboard.bounds - ); - artboard.draw(renderer); - renderer.restore(); - } - renderer.flush(); - - rive.requestAnimationFrame(draw); - } - rive.requestAnimationFrame(draw); + return { artboard, stateMachine, animation }; } async function main() { + // Determine how many Rives to load and draw onto the singular canvas const params = new Proxy(new URLSearchParams(window.location.search), { get: (searchParams, prop) => searchParams.get(prop), }); - const numCanvases = parseInt(params.numCanvases || 0) || 7; - const hasRandomSizes = !!params.hasRandomSizes || false; + const numRivesToRender = parseInt(params.numCanvases || 0) || 20; + + // Set canvas surface area based on the amount of rivs to render + // To keep this simple, we'll just render each Rive with an area + // of 250x250 + let canvas = document.getElementById("rive-canvas"); + canvas.width = `${numRivesToRender * 250}`; + canvas.height = `${Math.ceil(numRivesToRender / 4) * 250}`; + + // Instance Rive and create our Renderer + // Note: We use the advanced-single build here to simplify having to load in WASM, as + // this will ensure WASM is bundled in the JS const rive = await RiveCanvas(); + const renderer = rive.makeRenderer(canvas, true); - // Optionally perform leak checks right after rive is initialized. + // OPTIONAL FOR DEBUG TESTING: Perform leak checks right after rive is initialized. // await checkForLeaks(rive); - rive.enableFPSCounter(); - for (let i = 0; i < numCanvases; i++) { - await renderRiveAnimation({ rive, num: i, hasRandomSizes }); + // Track the artboard, animation/state machine of each Rive file we load in + let instances = []; + for (let i = 0; i < numRivesToRender; i++) { + instances.push(await retrieveRiveContents({ rive, num: i })); } -} -async function checkForLeaks(rive) { - const riveEx = RIVE_EXAMPLES[0]; - const { hasStateMachine } = riveEx; - var stateMachine, animation; - const bytes = await (await fetch(new Request(riveEx.riveFile))).arrayBuffer(); - const file = await rive.load(new Uint8Array(bytes)); - const artboard = file.defaultArtboard(); - artboard.advance(0); - if (hasStateMachine) { - stateMachine = new rive.StateMachineInstance( - artboard.stateMachineByIndex(0), - artboard - ); - } else { - animation = new rive.LinearAnimationInstance( - artboard.animationByName(riveEx.animation), - artboard - ); - } - const num = 0; - let canvas = document.getElementById(`canvas${num}`); - if (!canvas) { - const body = document.querySelector("body"); - canvas = document.createElement("canvas"); - canvas.id = `canvas${num}`; - body.appendChild(canvas); - } - canvas.width = "400"; - canvas.height = "400"; - // Don't use the offscreen renderer for FF as it should have a context limit of 300 - const renderer = rive.makeRenderer(canvas, true); - var elapsedSeconds = 0.0167; - // Render 20 frames. - for (var i = 0; i < 1000; i++) { + let lastTime = 0; + function draw(time) { + if (!lastTime) { + lastTime = time; + } + const elapsedMs = time - lastTime; + const elapsedSeconds = elapsedMs / 1000; + lastTime = time; + renderer.clear(); - if (artboard) { - if (stateMachine) { - stateMachine.advance(elapsedSeconds); + let trackX = 0; + let trackY = 0; + const colMax = 4; + + // For each Rive we loaded, advance the animation/state machine and artboard by elapsed + // time since last frame draw and render the Artboard to a piece of the canvas using + // the Renderer's align method + for (let i = 0; i < numRivesToRender; i++) { + let { artboard, stateMachine, animation } = instances[i]; + if (artboard) { + if (stateMachine) { + stateMachine.advance(elapsedSeconds); + } + if (animation) { + animation.advance(elapsedSeconds); + animation.apply(1); + } + artboard.advance(elapsedSeconds); + renderer.save(); + // Draw to a 250x250 piece of the canvas and track "position" + // in grid to move to the next piece to render the next Rive + renderer.align( + rive.Fit.contain, + rive.Alignment.center, + { + minX: trackX, + minY: trackY, + maxX: trackX + 250, + maxY: trackY + 250, + }, + artboard.bounds + ); + if ((i + 1) % colMax === 0) { + trackX = 0; + trackY += 250; + } else { + trackX += 250; + } + + // Pass along our Renderer to the artboard, so that it can draw onto the canvas + artboard.draw(renderer); + renderer.restore(); + renderer.flush(); } - if (animation) { - animation.advance(elapsedSeconds); - animation.apply(1); - } - artboard.advance(elapsedSeconds); - renderer.save(); - renderer.align( - rive.Fit.contain, - rive.Alignment.center, - { - minX: 0, - minY: 0, - maxX: canvas.width, - maxY: canvas.height, - }, - artboard.bounds - ); - artboard.draw(renderer); - renderer.restore(); } - renderer.flush(); - } - renderer.delete(); - if (stateMachine) { - stateMachine.delete(); - } - if (animation) { - animation.delete(); - } - if (artboard) { - artboard.delete(); + // Needed to actually resolve a queue of drawing and rendering calls with our Renderer + // Note: ONLY needed if using a normal JS requestAnimationFrame, rather than our wrapped + // one in the rive API + // rive.resolveAnimationFrame(); + + // Call the next frame! + rive.requestAnimationFrame(draw); } - file.delete(); - rive.cleanup(); - // Report any leaks. - rive.doLeakCheck(); - console.log("END"); + // Start the animation loop + rive.requestAnimationFrame(draw); } main(); diff --git a/wasm/js/renderer.js b/wasm/js/renderer.js index 360a225d..dc93fd83 100644 --- a/wasm/js/renderer.js +++ b/wasm/js/renderer.js @@ -1032,6 +1032,8 @@ Module["onRuntimeInitialized"] = function () { Module["disableFPSCounter"] = _animationCallbackHandler.disableFPSCounter; _animationCallbackHandler.onAfterCallbacks = flushCanvasRenderers; + Module["resolveAnimationFrame"] = flushCanvasRenderers; + Module["cleanup"] = function () { if (_rectanizer) { _rectanizer.delete(); diff --git a/wasm/js/skia_renderer.js b/wasm/js/skia_renderer.js index 4de8a276..8c2fd0df 100644 --- a/wasm/js/skia_renderer.js +++ b/wasm/js/skia_renderer.js @@ -2,7 +2,7 @@ const skiaOnRuntimeInitialized = Module["onRuntimeInitialized"]; Module["onRuntimeInitialized"] = function () { // If an initialize function is already configured, execute that first. - (skiaOnRuntimeInitialized && skiaOnRuntimeInitialized()); + skiaOnRuntimeInitialized && skiaOnRuntimeInitialized(); const _isFirefox = navigator.userAgent.match(/firefox|fxios/i); let _offscreenGL = null; @@ -298,22 +298,23 @@ Module["onRuntimeInitialized"] = function () { ); _animationCallbackHandler.onAfterCallbacks = flushOffscreenRenderers; + Module["resolveAnimationFrame"] = flushOffscreenRenderers; + let load = Module["load"]; Module["load"] = function ( - bytes, + bytes, fileAssetLoader, - enableRiveAssetCDN = true, + enableRiveAssetCDN = true ) { - const loader = new Module["FallbackFileAssetLoader"](); if (fileAssetLoader !== undefined) { loader.addLoader(fileAssetLoader); } - if (enableRiveAssetCDN) { + if (enableRiveAssetCDN) { const cdnLoader = new Module["CDNFileAssetLoader"](); loader.addLoader(cdnLoader); } - + return Promise.resolve(load(bytes, loader)); }; @@ -333,5 +334,5 @@ Module["onRuntimeInitialized"] = function () { Module["decodeImage"] = function (bytes, onComplete) { let image = Module["decodeImageSkia"](bytes); onComplete(image); - } + }; };