imported from "final" folder
This commit is contained in:
775
.pio/libdeps/esp01_1m/FastLED/legacy_index.js
Normal file
775
.pio/libdeps/esp01_1m/FastLED/legacy_index.js
Normal file
@@ -0,0 +1,775 @@
|
||||
/**
|
||||
* FastLED WebAssembly Compiler Main Module
|
||||
*
|
||||
* This module serves as the main entry point for the FastLED WebAssembly compiler.
|
||||
* It handles module loading, UI management, graphics rendering, file processing,
|
||||
* and the main setup/loop execution for FastLED programs.
|
||||
*
|
||||
* Key responsibilities:
|
||||
* - Loading and initializing the FastLED WASM module
|
||||
* - Managing UI components and user interactions
|
||||
* - Handling graphics rendering (both fast 2D and beautiful 3D modes)
|
||||
* - Processing frame data and LED strip updates
|
||||
* - File system operations and asset loading
|
||||
* - Audio integration and processing
|
||||
*
|
||||
* @module FastLED/Compiler
|
||||
*/
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
/* eslint-disable import/extensions */
|
||||
|
||||
import { JsonUiManager } from './modules/ui_manager.js';
|
||||
import { GraphicsManager } from './modules/graphics_manager.js';
|
||||
import { GraphicsManagerThreeJS } from './modules/graphics_manager_threejs.js';
|
||||
import { isDenseGrid } from './modules/graphics_utils.js';
|
||||
|
||||
/** URL parameters for runtime configuration */
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
/** Force fast 2D renderer when gfx=0 URL parameter is present */
|
||||
const FORCE_FAST_RENDERER = urlParams.get('gfx') === '0';
|
||||
|
||||
/** Force beautiful 3D renderer when gfx=1 URL parameter is present */
|
||||
const FORCE_THREEJS_RENDERER = urlParams.get('gfx') === '1';
|
||||
|
||||
/** Maximum number of lines to keep in stdout output display */
|
||||
const MAX_STDOUT_LINES = 50;
|
||||
|
||||
/** Default frame rate for FastLED animations (60 FPS) */
|
||||
const DEFAULT_FRAME_RATE_60FPS = 60;
|
||||
|
||||
/** Current frame rate setting */
|
||||
let frameRate = DEFAULT_FRAME_RATE_60FPS;
|
||||
|
||||
/** Flag indicating if canvas data has been received */
|
||||
const receivedCanvas = false;
|
||||
|
||||
/**
|
||||
* Screen mapping data structure containing strip-to-screen coordinate mappings
|
||||
* @type {Object} screenMap
|
||||
* @property {Object} strips - Map of strip ID to screen coordinate data
|
||||
* @property {number[]} absMin - Absolute minimum [x, y] coordinates
|
||||
* @property {number[]} absMax - Absolute maximum [x, y] coordinates
|
||||
*/
|
||||
const screenMap = {
|
||||
strips: {},
|
||||
absMin: [0, 0],
|
||||
absMax: [0, 0],
|
||||
};
|
||||
|
||||
/** HTML element ID for the main rendering canvas */
|
||||
let canvasId;
|
||||
|
||||
/** HTML element ID for the UI controls container */
|
||||
let uiControlsId;
|
||||
|
||||
/** HTML element ID for the output/console display */
|
||||
let outputId;
|
||||
|
||||
/** UI manager instance for handling user interface components */
|
||||
let uiManager;
|
||||
|
||||
/** Flag indicating if UI canvas settings have changed */
|
||||
let uiCanvasChanged = false;
|
||||
|
||||
/** Three.js modules container for 3D rendering */
|
||||
let threeJsModules = {};
|
||||
|
||||
/** Graphics manager instance (either 2D or 3D) */
|
||||
let graphicsManager;
|
||||
|
||||
/** Container ID for ThreeJS rendering context */
|
||||
let containerId;
|
||||
|
||||
/** Graphics configuration arguments */
|
||||
let graphicsArgs = {};
|
||||
|
||||
/**
|
||||
* Stub FastLED loader function (replaced during initialization)
|
||||
* @param {Object} options - Loading options
|
||||
* @returns {Promise<null>} Always returns null (stub implementation)
|
||||
*/
|
||||
let _loadFastLED = function (options) {
|
||||
// Stub to let the user/dev know that something went wrong.
|
||||
// This function is replaced with an async implementation, so it must be async for interface compatibility
|
||||
console.log('FastLED loader function was not set.');
|
||||
return Promise.resolve(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Public FastLED loading function (delegates to private implementation)
|
||||
* @async
|
||||
* @param {Object} options - Loading options and configuration
|
||||
* @returns {Promise<*>} Result from the FastLED loader
|
||||
*/
|
||||
export async function loadFastLED(options) {
|
||||
// This will be overridden through the initialization.
|
||||
return await _loadFastLED(options);
|
||||
}
|
||||
|
||||
/** Application start time epoch for timing calculations */
|
||||
const EPOCH = new Date().getTime();
|
||||
|
||||
/**
|
||||
* Gets elapsed time since application start
|
||||
* @returns {string} Time in seconds with one decimal place
|
||||
*/
|
||||
function getTimeSinceEpoc() {
|
||||
const outMS = new Date().getTime() - EPOCH;
|
||||
const outSec = outMS / 1000;
|
||||
// one decimal place
|
||||
return outSec.toFixed(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print function (will be overridden during initialization)
|
||||
* @function
|
||||
*/
|
||||
let print = function () {};
|
||||
|
||||
/** Store reference to original console for fallback */
|
||||
const prev_console = console;
|
||||
|
||||
// Store original console methods
|
||||
const _prev_log = prev_console.log;
|
||||
const _prev_warn = prev_console.warn;
|
||||
const _prev_error = prev_console.error;
|
||||
|
||||
/**
|
||||
* Adds timestamp to console arguments
|
||||
* @param {...*} args - Console arguments to timestamp
|
||||
* @returns {Array} Arguments array with timestamp prepended
|
||||
*/
|
||||
function toStringWithTimeStamp(...args) {
|
||||
const time = `${getTimeSinceEpoc()}s`;
|
||||
return [time, ...args]; // Return array with time prepended, don't join
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom console.log implementation with timestamps
|
||||
* @param {...*} args - Arguments to log
|
||||
*/
|
||||
function log(...args) {
|
||||
const argsWithTime = toStringWithTimeStamp(...args);
|
||||
_prev_log(...argsWithTime); // Spread the array when calling original logger
|
||||
try {
|
||||
print(...argsWithTime);
|
||||
} catch (e) {
|
||||
_prev_log('Error in log', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom console.warn implementation with timestamps
|
||||
* @param {...*} args - Arguments to warn about
|
||||
*/
|
||||
function warn(...args) {
|
||||
const argsWithTime = toStringWithTimeStamp(...args);
|
||||
_prev_warn(...argsWithTime);
|
||||
try {
|
||||
print(...argsWithTime);
|
||||
} catch (e) {
|
||||
_prev_warn('Error in warn', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom print function for displaying output in the UI
|
||||
* @param {...*} args - Arguments to print to UI output
|
||||
*/
|
||||
function customPrintFunction(...args) {
|
||||
if (containerId === undefined) {
|
||||
return; // Not ready yet.
|
||||
}
|
||||
// take the args and stringify them, then add them to the output element
|
||||
const cleanedArgs = args.map((arg) => {
|
||||
if (typeof arg === 'object') {
|
||||
try {
|
||||
return JSON.stringify(arg).slice(0, 100);
|
||||
} catch (e) {
|
||||
return `${arg}`;
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
|
||||
const output = document.getElementById(outputId);
|
||||
const allText = `${output.textContent + [...cleanedArgs].join(' ')}\n`;
|
||||
// split into lines, and if there are more than 100 lines, remove one.
|
||||
const lines = allText.split('\n');
|
||||
while (lines.length > MAX_STDOUT_LINES) {
|
||||
lines.shift();
|
||||
}
|
||||
output.textContent = lines.join('\n');
|
||||
}
|
||||
|
||||
// DO NOT OVERRIDE ERROR! When something goes really wrong we want it
|
||||
// to always go to the console. If we hijack it then startup errors become
|
||||
// extremely difficult to debug.
|
||||
|
||||
// Override console for custom logging behavior
|
||||
// Note: Modifying existing console properties instead of reassigning the global
|
||||
const originalConsole = console;
|
||||
console.log = log;
|
||||
console.warn = warn;
|
||||
console.error = _prev_error;
|
||||
|
||||
/**
|
||||
* Appends raw file data to WASM module file system
|
||||
* @param {Object} moduleInstance - The WASM module instance
|
||||
* @param {number} path_cstr - C string pointer to file path
|
||||
* @param {number} data_cbytes - C bytes pointer to file data
|
||||
* @param {number} len_int - Length of data in bytes
|
||||
*/
|
||||
function jsAppendFileRaw(moduleInstance, path_cstr, data_cbytes, len_int) {
|
||||
// Stream this chunk
|
||||
moduleInstance.ccall(
|
||||
'jsAppendFile',
|
||||
'number', // return value
|
||||
['number', 'number', 'number'], // argument types, not sure why numbers works.
|
||||
[path_cstr, data_cbytes, len_int],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends Uint8Array file data to WASM module file system
|
||||
* @param {Object} moduleInstance - The WASM module instance
|
||||
* @param {string} path - File path in the virtual file system
|
||||
* @param {Uint8Array} blob - File data as byte array
|
||||
*/
|
||||
function jsAppendFileUint8(moduleInstance, path, blob) {
|
||||
const n = moduleInstance.lengthBytesUTF8(path) + 1;
|
||||
const path_cstr = moduleInstance._malloc(n);
|
||||
moduleInstance.stringToUTF8(path, path_cstr, n);
|
||||
const ptr = moduleInstance._malloc(blob.length);
|
||||
moduleInstance.HEAPU8.set(blob, ptr);
|
||||
jsAppendFileRaw(moduleInstance, path_cstr, ptr, blob.length);
|
||||
moduleInstance._free(ptr);
|
||||
moduleInstance._free(path_cstr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates minimum and maximum values from coordinate arrays
|
||||
* @param {number[]} x_array - Array of X coordinates
|
||||
* @param {number[]} y_array - Array of Y coordinates
|
||||
* @returns {Array<Array<number>>} [[min_x, min_y], [max_x, max_y]]
|
||||
*/
|
||||
function minMax(x_array, y_array) {
|
||||
let min_x = x_array[0];
|
||||
let min_y = y_array[0];
|
||||
let max_x = x_array[0];
|
||||
let max_y = y_array[0];
|
||||
for (let i = 1; i < x_array.length; i++) {
|
||||
min_x = Math.min(min_x, x_array[i]);
|
||||
min_y = Math.min(min_y, y_array[i]);
|
||||
max_x = Math.max(max_x, x_array[i]);
|
||||
max_y = Math.max(max_y, y_array[i]);
|
||||
}
|
||||
return [[min_x, min_y], [max_x, max_y]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Partitions files into immediate and streaming categories based on extensions
|
||||
* @param {Array<Object>} filesJson - Array of file objects with path and data
|
||||
* @param {string[]} immediateExtensions - Extensions that should be loaded immediately
|
||||
* @returns {Array<Array<Object>>} [immediateFiles, streamingFiles]
|
||||
*/
|
||||
function partition(filesJson, immediateExtensions) {
|
||||
const immediateFiles = [];
|
||||
const streamingFiles = [];
|
||||
filesJson.map((file) => {
|
||||
for (const ext of immediateExtensions) {
|
||||
const pathLower = file.path.toLowerCase();
|
||||
if (pathLower.endsWith(ext.toLowerCase())) {
|
||||
immediateFiles.push(file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
streamingFiles.push(file);
|
||||
});
|
||||
return [immediateFiles, streamingFiles];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file manifest JSON for the WASM module
|
||||
* @param {Array<Object>} filesJson - Array of file objects
|
||||
* @param {number} frame_rate - Target frame rate for animations
|
||||
* @returns {Object} Manifest object with files and frameRate
|
||||
*/
|
||||
function getFileManifestJson(filesJson, frame_rate) {
|
||||
const trimmedFilesJson = filesJson.map((file) => ({
|
||||
path: file.path,
|
||||
size: file.size,
|
||||
}));
|
||||
const options = {
|
||||
files: trimmedFilesJson,
|
||||
frameRate: frame_rate,
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the canvas with new frame data from FastLED
|
||||
* @param {Array<Object>} frameData - Array of strip data with pixel information
|
||||
*/
|
||||
function updateCanvas(frameData) {
|
||||
// we are going to add the screenMap to the graphicsManager
|
||||
if (frameData.screenMap === undefined) {
|
||||
console.warn('Screen map not found in frame data, skipping canvas update');
|
||||
return;
|
||||
}
|
||||
if (!graphicsManager) {
|
||||
const isDenseMap = isDenseGrid(frameData);
|
||||
if (FORCE_THREEJS_RENDERER) {
|
||||
console.log('Creating Beautiful GraphicsManager with canvas ID (forced)', canvasId);
|
||||
graphicsManager = new GraphicsManagerThreeJS(graphicsArgs);
|
||||
} else if (FORCE_FAST_RENDERER) {
|
||||
console.log('Creating Fast GraphicsManager with canvas ID (forced)', canvasId);
|
||||
graphicsManager = new GraphicsManager(graphicsArgs);
|
||||
} else if (isDenseMap) {
|
||||
console.log('Creating Fast GraphicsManager with canvas ID', canvasId);
|
||||
graphicsManager = new GraphicsManager(graphicsArgs);
|
||||
} else {
|
||||
console.log('Creating Beautiful GraphicsManager with canvas ID', canvasId);
|
||||
graphicsManager = new GraphicsManagerThreeJS(graphicsArgs);
|
||||
}
|
||||
uiCanvasChanged = false;
|
||||
}
|
||||
|
||||
if (uiCanvasChanged) {
|
||||
uiCanvasChanged = false;
|
||||
graphicsManager.reset();
|
||||
}
|
||||
|
||||
graphicsManager.updateCanvas(frameData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main setup and loop execution function for FastLED programs
|
||||
* @param {Function} extern_setup - Setup function from the WASM module
|
||||
* @param {Function} extern_loop - Loop function from the WASM module
|
||||
* @param {number} frame_rate - Target frame rate for the animation loop
|
||||
*/
|
||||
function FastLED_SetupAndLoop(extern_setup, extern_loop, frame_rate) {
|
||||
extern_setup();
|
||||
console.log('Starting loop...');
|
||||
const frameInterval = 1000 / frame_rate;
|
||||
let lastFrameTime = 0;
|
||||
|
||||
/**
|
||||
* Animation loop function that maintains consistent frame rate
|
||||
* @param {number} currentTime - Current timestamp from requestAnimationFrame
|
||||
*/
|
||||
function runLoop(currentTime) {
|
||||
if (currentTime - lastFrameTime >= frameInterval) {
|
||||
extern_loop();
|
||||
lastFrameTime = currentTime;
|
||||
}
|
||||
requestAnimationFrame(runLoop);
|
||||
}
|
||||
requestAnimationFrame(runLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles strip update events from the FastLED library
|
||||
* Processes screen mapping configuration and canvas setup
|
||||
* @param {Object} jsonData - Strip update data from FastLED
|
||||
* @param {string} jsonData.event - Event type (e.g., 'set_canvas_map')
|
||||
* @param {number} jsonData.strip_id - ID of the LED strip
|
||||
* @param {Object} jsonData.map - Coordinate mapping data
|
||||
* @param {number} [jsonData.diameter] - LED diameter in mm (default: 0.2)
|
||||
*/
|
||||
function FastLED_onStripUpdate(jsonData) {
|
||||
// Hooks into FastLED to receive updates from the FastLED library related
|
||||
// to the strip state. This is where the ScreenMap will be effectively set.
|
||||
// uses global variables.
|
||||
console.log('Received strip update:', jsonData);
|
||||
const { event } = jsonData;
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let eventHandled = false;
|
||||
if (event === 'set_canvas_map') {
|
||||
eventHandled = true;
|
||||
// Work in progress.
|
||||
const { map } = jsonData;
|
||||
console.log('Received map:', jsonData);
|
||||
const [min, max] = minMax(map.x, map.y);
|
||||
console.log('min', min, 'max', max);
|
||||
|
||||
const stripId = jsonData.strip_id;
|
||||
const isUndefined = (value) => typeof value === 'undefined';
|
||||
if (isUndefined(stripId)) {
|
||||
throw new Error('strip_id is required for set_canvas_map event');
|
||||
}
|
||||
|
||||
let diameter = jsonData.diameter;
|
||||
if (diameter === undefined) {
|
||||
const stripId = jsonData.strip_id;
|
||||
console.warn(`Diameter was unset for strip ${stripId}, assuming default value of 2 mm.`);
|
||||
diameter = 0.2;
|
||||
}
|
||||
|
||||
screenMap.strips[stripId] = {
|
||||
map,
|
||||
min,
|
||||
max,
|
||||
diameter: diameter,
|
||||
};
|
||||
console.log('Screen map updated:', screenMap);
|
||||
// iterate through all the screenMaps and get the absolute min and max
|
||||
const absMin = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
|
||||
const absMax = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
||||
let setAtLeastOnce = false;
|
||||
for (const stripId in screenMap.strips) {
|
||||
if (!Object.prototype.hasOwnProperty.call(screenMap.strips, stripId)) continue;
|
||||
console.log('Processing strip ID', stripId);
|
||||
const id = Number.parseInt(stripId, 10);
|
||||
const stripData = screenMap.strips[id];
|
||||
absMin[0] = Math.min(absMin[0], stripData.min[0]);
|
||||
absMin[1] = Math.min(absMin[1], stripData.min[1]);
|
||||
absMax[0] = Math.max(absMax[0], stripData.max[0]);
|
||||
absMax[1] = Math.max(absMax[1], stripData.max[1]);
|
||||
// if diff x = 0, expand by one on each direction.
|
||||
if (absMin[0] === absMax[0]) {
|
||||
absMin[0] = absMin[0] - 1;
|
||||
absMax[0] = absMax[0] + 1;
|
||||
}
|
||||
// if diff y = 0, expand by one on each direction.
|
||||
if (absMin[1] === absMax[1]) {
|
||||
absMin[1] = absMin[1] - 1;
|
||||
absMax[1] = absMax[1] + 1;
|
||||
}
|
||||
setAtLeastOnce = true;
|
||||
}
|
||||
if (!setAtLeastOnce) {
|
||||
console.error('No screen map data found, skipping canvas size update');
|
||||
return;
|
||||
}
|
||||
screenMap.absMin = absMin;
|
||||
screenMap.absMax = absMax;
|
||||
width = Number.parseInt(absMax[0] - absMin[0], 10) + 1;
|
||||
height = Number.parseInt(absMax[1] - absMin[1], 10) + 1;
|
||||
console.log('canvas updated with width and height', width, height);
|
||||
// now update the canvas size.
|
||||
const canvas = document.getElementById(canvasId);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
uiCanvasChanged = true;
|
||||
console.log('Screen map updated:', screenMap);
|
||||
}
|
||||
|
||||
if (!eventHandled) {
|
||||
console.warn(`We do not support event ${event} yet.`);
|
||||
return;
|
||||
}
|
||||
if (receivedCanvas) {
|
||||
console.warn(
|
||||
'Canvas size has already been set, setting multiple canvas sizes is not supported yet and the previous one will be overwritten.',
|
||||
);
|
||||
}
|
||||
const canvas = document.getElementById(canvasId);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
// Set display size (CSS pixels) to 640px width while maintaining aspect ratio
|
||||
const displayWidth = 640;
|
||||
const displayHeight = Math.round((height / width) * displayWidth);
|
||||
|
||||
// Set CSS display size while maintaining aspect ratio
|
||||
canvas.style.width = `${displayWidth}px`;
|
||||
canvas.style.height = `${displayHeight}px`;
|
||||
console.log(
|
||||
`Canvas size set to ${width}x${height}, displayed at ${canvas.style.width}x${canvas.style.height} `,
|
||||
);
|
||||
// unconditionally delete the graphicsManager
|
||||
if (graphicsManager) {
|
||||
graphicsManager.reset();
|
||||
graphicsManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles new LED strip registration events
|
||||
* @param {number} stripId - Unique identifier for the LED strip
|
||||
* @param {number} stripLength - Number of LEDs in the strip
|
||||
*/
|
||||
function FastLED_onStripAdded(stripId, stripLength) {
|
||||
// uses global variables.
|
||||
const output = document.getElementById(outputId);
|
||||
output.textContent += `Strip added: ID ${stripId}, length ${stripLength}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main frame processing function called by FastLED for each animation frame
|
||||
* @param {Array<Object>} frameData - Array of strip data with pixel colors
|
||||
* @param {Function} uiUpdateCallback - Callback to send UI changes back to FastLED
|
||||
*/
|
||||
function FastLED_onFrame(frameData, uiUpdateCallback) {
|
||||
// uiUpdateCallback is a function from FastLED that will parse a json string
|
||||
// representing the changes to the UI that FastLED will need to respond to.
|
||||
// uses global variables.
|
||||
const changesJson = uiManager.processUiChanges();
|
||||
if (changesJson !== null) {
|
||||
const changesJsonStr = JSON.stringify(changesJson);
|
||||
uiUpdateCallback(changesJsonStr);
|
||||
}
|
||||
if (frameData.length === 0) {
|
||||
console.warn('Received empty frame data, skipping update');
|
||||
// New experiment try to run anyway.
|
||||
// return;
|
||||
}
|
||||
frameData.screenMap = screenMap;
|
||||
updateCanvas(frameData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles UI element addition events from FastLED
|
||||
* @param {Object} jsonData - UI element configuration data
|
||||
*/
|
||||
function FastLED_onUiElementsAdded(jsonData) {
|
||||
// uses global variables.
|
||||
uiManager.addUiElements(jsonData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to initialize and start the FastLED setup/loop cycle
|
||||
* @async
|
||||
* @param {Function} extern_setup - Setup function from WASM module
|
||||
* @param {Function} extern_loop - Loop function from WASM module
|
||||
* @param {number} frame_rate - Target frame rate for animations
|
||||
* @param {Object} moduleInstance - The loaded WASM module instance
|
||||
* @param {Array<Object>} filesJson - Array of files to load into the virtual filesystem
|
||||
*/
|
||||
async function fastledLoadSetupLoop(
|
||||
extern_setup,
|
||||
extern_loop,
|
||||
frame_rate,
|
||||
moduleInstance,
|
||||
filesJson,
|
||||
) {
|
||||
console.log('Calling setup function...');
|
||||
|
||||
const fileManifest = getFileManifestJson(filesJson, frame_rate);
|
||||
moduleInstance.cwrap('fastled_declare_files', null, ['string'])(JSON.stringify(fileManifest));
|
||||
console.log('Files JSON:', filesJson);
|
||||
|
||||
/**
|
||||
* Processes a single file by streaming it to the WASM module
|
||||
* @async
|
||||
* @param {Object} file - File object with path and data
|
||||
* @param {string} file.path - File path in the virtual filesystem
|
||||
* @param {number} file.size - File size in bytes
|
||||
*/
|
||||
const processFile = async (file) => {
|
||||
try {
|
||||
const response = await fetch(file.path);
|
||||
const reader = response.body.getReader();
|
||||
|
||||
console.log(`File fetched: ${file.path}, size: ${file.size}`);
|
||||
|
||||
while (true) {
|
||||
// deno-lint-ignore no-await-in-loop
|
||||
const { value, done } = await reader.read();
|
||||
if (done) break;
|
||||
// Allocate and copy chunk data
|
||||
jsAppendFileUint8(moduleInstance, file.path, value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing file ${file.path}:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all files in parallel and calls completion callback
|
||||
* @async
|
||||
* @param {Array<Object>} filesJson - Array of file objects to fetch
|
||||
* @param {Function} [onComplete] - Optional callback when all files are loaded
|
||||
*/
|
||||
const fetchAllFiles = async (filesJson, onComplete) => {
|
||||
const promises = filesJson.map(async (file) => {
|
||||
await processFile(file);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
};
|
||||
|
||||
// Bind the functions to the global scope.
|
||||
globalThis.FastLED_onUiElementsAdded = FastLED_onUiElementsAdded;
|
||||
globalThis.FastLED_onFrame = FastLED_onFrame;
|
||||
globalThis.FastLED_onStripAdded = FastLED_onStripAdded;
|
||||
globalThis.FastLED_onStripUpdate = FastLED_onStripUpdate;
|
||||
|
||||
// Come back to this later - we want to partition the files into immediate and streaming files
|
||||
// so that large projects don't try to download ALL the large files BEFORE setup/loop is called.
|
||||
const [immediateFiles, streamingFiles] = partition(filesJson, ['.json', '.csv', '.txt', '.cfg']);
|
||||
console.log(
|
||||
'The following files will be immediatly available and can be read during setup():',
|
||||
immediateFiles,
|
||||
);
|
||||
console.log('The following files will be streamed in during loop():', streamingFiles);
|
||||
|
||||
const promiseImmediateFiles = fetchAllFiles(immediateFiles, () => {
|
||||
if (immediateFiles.length !== 0) {
|
||||
console.log('All immediate files downloaded to FastLED.');
|
||||
}
|
||||
});
|
||||
await promiseImmediateFiles;
|
||||
if (streamingFiles.length > 0) {
|
||||
const streamingFilesPromise = fetchAllFiles(streamingFiles, () => {
|
||||
console.log('All streaming files downloaded to FastLED.');
|
||||
});
|
||||
const delay = new Promise((r) => setTimeout(r, 250));
|
||||
// Wait for either the time delay or the streaming files to be processed, whichever
|
||||
// happens first.
|
||||
await Promise.any([delay, streamingFilesPromise]);
|
||||
}
|
||||
|
||||
console.log('Starting fastled');
|
||||
FastLED_SetupAndLoop(extern_setup, extern_loop, frame_rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function executed when the WASM module is loaded
|
||||
* Sets up the module loading infrastructure
|
||||
* @param {Function} fastLedLoader - The FastLED loader function
|
||||
*/
|
||||
function onModuleLoaded(fastLedLoader) {
|
||||
// Unpack the module functions and send them to the fastledLoadSetupLoop function
|
||||
|
||||
/**
|
||||
* Internal function to start FastLED with loaded module
|
||||
* @param {Object} moduleInstance - The loaded WASM module instance
|
||||
* @param {number} frameRate - Target frame rate for animations
|
||||
* @param {Array<Object>} filesJson - Files to load into virtual filesystem
|
||||
*/
|
||||
function __fastledLoadSetupLoop(moduleInstance, frameRate, filesJson) {
|
||||
const exports_exist = moduleInstance && moduleInstance._extern_setup &&
|
||||
moduleInstance._extern_loop;
|
||||
if (!exports_exist) {
|
||||
console.error('FastLED setup or loop functions are not available.');
|
||||
return;
|
||||
}
|
||||
|
||||
fastledLoadSetupLoop(
|
||||
moduleInstance._extern_setup,
|
||||
moduleInstance._extern_loop,
|
||||
frameRate,
|
||||
moduleInstance,
|
||||
filesJson,
|
||||
);
|
||||
}
|
||||
// Start fetch now in parallel
|
||||
|
||||
/**
|
||||
* Fetches and parses JSON from a given file path
|
||||
* @async
|
||||
* @param {string} fetchFilePath - Path to the JSON file to fetch
|
||||
* @returns {Promise<Object>} Parsed JSON data
|
||||
*/
|
||||
const fetchJson = async (fetchFilePath) => {
|
||||
const response = await fetch(fetchFilePath);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
const filesJsonPromise = fetchJson('files.json');
|
||||
try {
|
||||
if (typeof fastLedLoader === 'function') {
|
||||
// Load the module
|
||||
fastLedLoader().then(async (instance) => {
|
||||
console.log('Module loaded, running FastLED...');
|
||||
|
||||
// Expose the updateUiComponents method to the C++ module
|
||||
// This should be called BY C++ TO UPDATE the frontend, not the other way around
|
||||
instance._jsUiManager_updateUiComponents = function (jsonString) {
|
||||
console.log('*** C++ CALLING JS: updateUiComponents with:', jsonString);
|
||||
if (window.uiManagerInstance && window.uiManagerInstance.updateUiComponents) {
|
||||
window.uiManagerInstance.updateUiComponents(jsonString);
|
||||
} else {
|
||||
console.error('*** UI BINDING ERROR: uiManagerInstance not available ***');
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the files.json to load.
|
||||
let filesJson = null;
|
||||
try {
|
||||
filesJson = await filesJsonPromise;
|
||||
console.log('Files JSON:', filesJson);
|
||||
} catch (error) {
|
||||
console.error('Error fetching files.json:', error);
|
||||
filesJson = {};
|
||||
}
|
||||
__fastledLoadSetupLoop(instance, frameRate, filesJson);
|
||||
}).catch((err) => {
|
||||
console.error('Error loading fastled as a module:', err);
|
||||
});
|
||||
} else {
|
||||
console.log(
|
||||
'Could not detect a valid module loading for FastLED, expected function but got',
|
||||
typeof fastledLoader,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load FastLED:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main FastLED loading and initialization function
|
||||
* Sets up the entire FastLED environment including UI, graphics, and WASM module
|
||||
* @async
|
||||
* @param {Object} options - Configuration options for FastLED initialization
|
||||
* @param {string} options.canvasId - ID of the HTML canvas element for rendering
|
||||
* @param {string} options.uiControlsId - ID of the HTML element for UI controls
|
||||
* @param {string} options.printId - ID of the HTML element for console output
|
||||
* @param {number} [options.frameRate] - Target frame rate (defaults to 60 FPS)
|
||||
* @param {Object} options.threeJs - Three.js configuration object
|
||||
* @param {Object} options.threeJs.modules - Three.js module imports
|
||||
* @param {string} options.threeJs.containerId - Container ID for Three.js rendering
|
||||
* @param {Function} options.fastled - FastLED WASM module loader function
|
||||
* @returns {Promise<void>} Promise that resolves when FastLED is fully loaded
|
||||
*/
|
||||
async function localLoadFastLed(options) {
|
||||
try {
|
||||
console.log('Loading FastLED with options:', options);
|
||||
canvasId = options.canvasId;
|
||||
uiControlsId = options.uiControlsId;
|
||||
outputId = options.printId;
|
||||
print = customPrintFunction;
|
||||
console.log('Loading FastLED with options:', options);
|
||||
frameRate = options.frameRate || DEFAULT_FRAME_RATE_60FPS;
|
||||
uiManager = new JsonUiManager(uiControlsId);
|
||||
|
||||
// Expose UI manager globally for debug functions and C++ module
|
||||
window.uiManager = uiManager;
|
||||
window.uiManagerInstance = uiManager;
|
||||
|
||||
// Apply pending debug mode setting if it was set before manager creation
|
||||
if (typeof window._pendingUiDebugMode !== 'undefined') {
|
||||
uiManager.setDebugMode(window._pendingUiDebugMode);
|
||||
delete window._pendingUiDebugMode;
|
||||
}
|
||||
|
||||
const { threeJs } = options;
|
||||
console.log('ThreeJS:', threeJs);
|
||||
const fastLedLoader = options.fastled;
|
||||
threeJsModules = threeJs.modules;
|
||||
containerId = threeJs.containerId;
|
||||
console.log('ThreeJS modules:', threeJsModules);
|
||||
console.log('Container ID:', containerId);
|
||||
graphicsArgs = {
|
||||
canvasId,
|
||||
threeJsModules,
|
||||
};
|
||||
await onModuleLoaded(fastLedLoader);
|
||||
} catch (error) {
|
||||
console.error('Error loading FastLED:', error);
|
||||
// Debug point removed for linting compliance
|
||||
}
|
||||
}
|
||||
|
||||
/** Replace the stub loader with the actual implementation */
|
||||
_loadFastLED = localLoadFastLed;
|
||||
Reference in New Issue
Block a user