Documentation/Source example
Example source

samples/worker/04. Playlist Album Gallery.js

Referenced by:Worker
'use strict';

// Worker sample 4: Playlist Album Gallery
// Demonstrates: cloning playlist handles, Worker-side album deduplication, asynchronous
// artwork loading, thumbnail resizing and transferring GDI/D2D images back to a live panel UI.
// Engine switches between the panel-side baseline and the Worker pipeline so the effect on
// UI responsiveness and useful artwork concurrency can be compared directly.
// Mouse wheel changes cover size. Only the albums that fit in the current viewport are
// requested, and every settled size reloads them from scratch on purpose: this sample has
// no artwork cache. Each image is resized in the Worker so its shorter side matches the
// current cover size; panel drawing then only crops the already-small thumbnail. In Worker mode
// resize runs in the Worker; in Main mode it deliberately runs in the panel realm as a baseline.
// Images are drawn directly in on_paint as they arrive; no offscreen gallery bitmap is composed.
// Concurrency controls how many album-art
// promises may be in flight at once. Worker GDI uses the runtime's eager WIC decode path, while
// Main GDI keeps the legacy GDI+ path. The status separates first display, load, panel receive/display, and paint cost.

let engine = 'worker';
let drawMode = 1;
window.DrawMode = drawMode;

const DT_LEFT = 0x0000;
const DT_CENTER = 0x0001;
const DT_VCENTER = 0x0004;
const DT_SINGLELINE = 0x0020;
const DT_NOPREFIX = 0x0800;
const DT_END_ELLIPSIS = 0x8000;

const MARGIN = 12;
const HEADER_H = 64;
const GAP = 10;
const TEXT_H = 38;
const COVER_MIN = 72;
const COVER_MAX = 288;
const COVER_STEP = 16;
const LOAD_DEBOUNCE_MS = 70;
const CONCURRENCY_CHOICES = [4, 8, 16, 0];

let coverSize = 144;
let switchingRenderer = false;
let worker = null;
let generation = 0;
let loadRequestId = 0;
let loadTimer = 0;
let lastCapacity = 0;
let concurrency = 8;
let mainLoadToken = 0;

let playlistName = '';
let albums = [];
let images = [];
let loadState = { requestId: 0, count: 0, completed: 0, loaded: 0, firstDisplayElapsed: 0, loadElapsed: 0, receiveElapsed: 0, displayElapsed: 0, paintCount: 0, paintElapsed: 0, started: 0, active: false };

let TITLE_FONT;
let INFO_FONT;
let ALBUM_FONT;
let META_FONT;

function createFonts() {
    TITLE_FONT = gdi.Font('Segoe UI', 15, 1);
    INFO_FONT = gdi.Font('Segoe UI', 11);
    ALBUM_FONT = gdi.Font('Segoe UI', 11, 1);
    META_FONT = gdi.Font('Segoe UI', 10);
}

createFonts();

function resizeArtwork(image, size) {
    if (!image || size <= 0) return image;

    const width = image.Width;
    const height = image.Height;
    if (width <= 0 || height <= 0) return image;

    let targetWidth;
    let targetHeight;
    if (width >= height) {
        targetHeight = size;
        targetWidth = Math.max(size, Math.round(width * size / height));
    } else {
        targetWidth = size;
        targetHeight = Math.max(size, Math.round(height * size / width));
    }

    if (targetWidth === width && targetHeight === height) return image;
    return image.Resize(targetWidth, targetHeight, 2);
}

function buildAlbumsMain(handles) {
    const artists = fb.TitleFormat('$if2(%album artist%,%artist%)').EvalWithMetadbs(handles);
    const titles = fb.TitleFormat('$if2(%album%,%title%)').EvalWithMetadbs(handles);
    const years = fb.TitleFormat('$if2($year(%date%),)').EvalWithMetadbs(handles);
    const map = new Map();
    const result = [];

    for (let i = 0; i < handles.Count; ++i) {
        const artist = artists[i] || '(unknown artist)';
        const title = titles[i] || '(unknown album)';
        const year = years[i] || '';
        const key = artist + '\0' + title;

        let album = map.get(key);
        if (!album) {
            album = { artist, title, year, tracks: 0, handle: handles[i] };
            map.set(key, album);
            result.push(album);
        }
        ++album.tracks;
    }

    return result;
}

const workerSource = String.raw`
'use strict';

let currentGeneration = 0;
let activeLoadId = 0;
let albums = [];

function resizeArtwork(image, size) {
    if (!image || size <= 0) return image;

    const width = image.Width;
    const height = image.Height;
    if (width <= 0 || height <= 0) return image;

    let targetWidth;
    let targetHeight;
    if (width >= height) {
        targetHeight = size;
        targetWidth = Math.max(size, Math.round(width * size / height));
    } else {
        targetWidth = size;
        targetHeight = Math.max(size, Math.round(height * size / width));
    }

    if (targetWidth === width && targetHeight === height) return image;
    return image.Resize(targetWidth, targetHeight, 2);
}

function buildAlbums(handles) {
    const artists = fb.TitleFormat('$if2(%album artist%,%artist%)').EvalWithMetadbs(handles);
    const titles = fb.TitleFormat('$if2(%album%,%title%)').EvalWithMetadbs(handles);
    const years = fb.TitleFormat('$if2($year(%date%),)').EvalWithMetadbs(handles);
    const map = new Map();
    const result = [];

    for (let i = 0; i < handles.Count; ++i) {
        const artist = artists[i] || '(unknown artist)';
        const title = titles[i] || '(unknown album)';
        const year = years[i] || '';
        const key = artist + '\\0' + title;

        let album = map.get(key);
        if (!album) {
            album = { artist, title, year, tracks: 0, handle: handles[i] };
            map.set(key, album);
            result.push(album);
        }
        ++album.tracks;
    }

    return result;
}

function publicAlbums() {
    return albums.map(album => ({
        artist: album.artist,
        title: album.title,
        year: album.year,
        tracks: album.tracks
    }));
}

async function loadVisible(message) {
    const requestId = message.requestId;
    const generation = message.generation;
    const count = Math.min(Math.max(0, message.count | 0), albums.length);
    const requestedConcurrency = message.concurrency | 0;
    const coverSize = Math.max(1, message.coverSize | 0);
    const limit = requestedConcurrency > 0 ? Math.min(requestedConcurrency, count) : count;
    activeLoadId = requestId;
    const started = performance.now();
    let nextIndex = 0;

    async function runQueue() {
        while (generation === currentGeneration && requestId === activeLoadId) {
            const index = nextIndex++;
            if (index >= count) return;

            const album = albums[index];
            let image = null;
            try {
                const result = await utils.GetAlbumArtAsyncV2(0, album.handle, 0, false, false, false);
                if (result && result.image) image = resizeArtwork(result.image, coverSize);
            } catch (_) {
                image = null;
            }

            if (generation !== currentGeneration || requestId !== activeLoadId) return;

            const elapsed = performance.now() - started;
            if (image) {
                postMessage({ type: 'art', generation, requestId, index, elapsed, image }, [image]);
            } else {
                postMessage({ type: 'art', generation, requestId, index, elapsed, image: null });
            }
        }
    }

    const jobs = [];
    for (let i = 0; i < limit; ++i) jobs.push(runQueue());
    await Promise.all(jobs);
    if (generation !== currentGeneration || requestId !== activeLoadId) return;

    postMessage({
        type: 'complete',
        generation,
        requestId,
        elapsed: performance.now() - started
    });
}

onmessage = function (event) {
    const message = event.data;

    if (message.type === 'playlist') {
        currentGeneration = message.generation;
        activeLoadId = 0;
        albums = buildAlbums(message.handles);
        postMessage({
            type: 'catalog',
            generation: currentGeneration,
            playlistName: message.playlistName || '',
            albums: publicAlbums()
        });
        return;
    }

    if (message.type === 'cancel') {
        if (message.generation === currentGeneration) activeLoadId = 0;
        return;
    }

    if (message.type === 'load' && message.generation === currentGeneration) {
        loadVisible(message);
    }
};
`;

function engineRects() {
    const y = 7;
    const x = Math.max(8, window.Width - 342);
    return {
        label: { x, y, w: 46, h: 26 },
        main: { x: x + 48, y, w: 48, h: 26 },
        worker: { x: x + 100, y, w: 60, h: 26 }
    };
}

function rendererRects() {
    const y = 7;
    const x = Math.max(8, window.Width - 342) + 168;
    return {
        label: { x, y, w: 62, h: 26 },
        gdi: { x: x + 64, y, w: 48, h: 26 },
        d2d: { x: x + 116, y, w: 54, h: 26 }
    };
}

function concurrencyRects() {
    const y = 34;
    const x = Math.max(8, window.Width - 246);
    return {
        label: { x, y, w: 78, h: 24 },
        choices: [
            { value: 4, rect: { x: x + 80, y, w: 30, h: 24 }, text: '4' },
            { value: 8, rect: { x: x + 114, y, w: 30, h: 24 }, text: '8' },
            { value: 16, rect: { x: x + 148, y, w: 34, h: 24 }, text: '16' },
            { value: 0, rect: { x: x + 186, y, w: 48, h: 24 }, text: 'All' }
        ]
    };
}

function contains(rect, x, y) {
    return x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h;
}

function drawChoice(gr, rect, text, selected) {
    gr.FillSolidRect(rect.x, rect.y, rect.w, rect.h, selected ? 0xFF355D7A : 0xFF20262E);
    gr.DrawText(text, INFO_FONT, selected ? 0xFFFFFFFF : 0xFFB7C1CB,
        rect.x, rect.y, rect.w, rect.h, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
}

function getLayout() {
    const contentW = Math.max(1, window.Width - MARGIN * 2);
    const contentH = Math.max(1, window.Height - HEADER_H - MARGIN);
    const cellW = coverSize;
    const cellH = coverSize + TEXT_H;

    const columns = Math.max(1, Math.floor((contentW + GAP) / (cellW + GAP)));
    const rows = Math.max(1, Math.floor((contentH + GAP) / (cellH + GAP)));
    const capacity = columns * rows;
    const totalW = columns * cellW + (columns - 1) * GAP;
    const firstX = MARGIN + Math.max(0, Math.floor((contentW - totalW) * 0.5));

    return { columns, rows, capacity, firstX, top: HEADER_H, cellW, cellH };
}

function tileRect(index, layout) {
    const col = index % layout.columns;
    const row = Math.floor(index / layout.columns);
    return {
        x: layout.firstX + col * (layout.cellW + GAP),
        y: layout.top + row * (layout.cellH + GAP),
        w: layout.cellW,
        h: layout.cellH
    };
}

function drawCover(gr, image, x, y, size) {
    const srcAspect = image.Width / image.Height;
    let sx = 0;
    let sy = 0;
    let sw = image.Width;
    let sh = image.Height;

    if (srcAspect > 1) {
        sw = image.Height;
        sx = (image.Width - sw) * 0.5;
    } else if (srcAspect < 1) {
        sh = image.Width;
        sy = (image.Height - sh) * 0.5;
    }

    gr.DrawImage(image, x, y, size, size, sx, sy, sw, sh);
}

function statusText() {
    const visible = Math.min(albums.length, getLayout().capacity);
    let text = coverSize + ' px  •  visible ' + visible + '/' + albums.length;

    if (loadState.count) {
        text += '  •  ' + loadState.completed + '/' + loadState.count;
        if (loadState.firstDisplayElapsed > 0) text += '  •  first ' + loadState.firstDisplayElapsed.toFixed(0) + ' ms';
        if (loadState.loadElapsed > 0) text += '  •  load ' + loadState.loadElapsed.toFixed(0) + ' ms';
        if (loadState.receiveElapsed > 0) text += '  •  receive ' + loadState.receiveElapsed.toFixed(0) + ' ms';
        if (loadState.displayElapsed > 0) text += '  •  display ' + loadState.displayElapsed.toFixed(0) + ' ms';
        if (loadState.paintCount > 0) text += '  •  paints ' + loadState.paintCount + '/' + loadState.paintElapsed.toFixed(0) + ' ms';
    }

    return text;
}

async function loadVisibleMain(requestId, count, token) {
    const started = performance.now();
    const limit = concurrency > 0 ? Math.min(concurrency, count) : count;
    let nextIndex = 0;

    async function runQueue() {
        while (engine === 'main' && token === mainLoadToken && requestId === loadState.requestId) {
            const index = nextIndex++;
            if (index >= count) return;

            const album = albums[index];
            let image = null;
            try {
                const result = await utils.GetAlbumArtAsyncV2(0, album.handle, 0, false, false, false);
                if (result && result.image) image = resizeArtwork(result.image, coverSize);
            } catch (_) {
                image = null;
            }

            if (engine !== 'main' || token !== mainLoadToken || requestId !== loadState.requestId) return;

            ++loadState.completed;
            if (image) {
                images[index] = image;
                ++loadState.loaded;
            }

            const elapsed = performance.now() - started;
            loadState.loadElapsed = elapsed;
            if (loadState.completed >= loadState.count && loadState.receiveElapsed <= 0) {
                loadState.receiveElapsed = elapsed;
            }

            const rect = tileRect(index, getLayout());
            window.RepaintRect(rect.x, rect.y, rect.w, rect.h);
            window.RepaintRect(MARGIN, 32, Math.max(1, window.Width - 220), 20);
        }
    }

    const jobs = [];
    for (let i = 0; i < limit; ++i) jobs.push(runQueue());
    await Promise.all(jobs);

    if (engine !== 'main' || token !== mainLoadToken || requestId !== loadState.requestId) return;

    const elapsed = performance.now() - started;
    loadState.active = false;
    loadState.loadElapsed = elapsed;
    if (loadState.receiveElapsed <= 0) loadState.receiveElapsed = elapsed;
    window.RepaintRect(MARGIN, 32, Math.max(1, window.Width - 220), 20);
}

function cancelCurrentLoad() {
    ++mainLoadToken;
    if (worker) worker.postMessage({ type: 'cancel', generation });
    loadState.active = false;
}

function beginVisibleLoad() {
    loadTimer = 0;
    if (!albums.length) return;
    if (engine === 'worker' && !worker) return;

    const count = Math.min(albums.length, getLayout().capacity);
    const requestId = ++loadRequestId;
    const token = ++mainLoadToken;

    images = new Array(count).fill(null);
    loadState = { requestId, count, completed: 0, loaded: 0, firstDisplayElapsed: 0, loadElapsed: 0, receiveElapsed: 0, displayElapsed: 0, paintCount: 0, paintElapsed: 0, started: performance.now(), active: true };

    if (engine === 'worker') {
        worker.postMessage({
            type: 'load',
            generation,
            requestId,
            count,
            concurrency,
            coverSize
        });
    } else {
        loadVisibleMain(requestId, count, token);
    }
    window.Repaint();
}

function scheduleVisibleLoad(delay = LOAD_DEBOUNCE_MS) {
    if (loadTimer) clearTimeout(loadTimer);
    cancelCurrentLoad();
    loadTimer = setTimeout(beginVisibleLoad, delay);
}

function sendPlaylist() {
    if (engine === 'worker' && !worker) return;

    if (loadTimer) {
        clearTimeout(loadTimer);
        loadTimer = 0;
    }
    ++mainLoadToken;

    albums = [];
    images = [];
    loadState = { requestId: 0, count: 0, completed: 0, loaded: 0, firstDisplayElapsed: 0, loadElapsed: 0, receiveElapsed: 0, displayElapsed: 0, paintCount: 0, paintElapsed: 0, started: 0, active: false };
    lastCapacity = 0;

    const playlistIndex = plman.ActivePlaylist;
    const handles = playlistIndex >= 0 ? plman.GetPlaylistItems(playlistIndex) : fb.CreateHandleList();
    playlistName = playlistIndex >= 0 ? plman.GetPlaylistName(playlistIndex) : 'No active playlist';

    ++generation;
    if (engine === 'worker') {
        worker.postMessage({
            type: 'playlist',
            generation,
            playlistName,
            handles
        });
    } else {
        albums = buildAlbumsMain(handles);
        lastCapacity = getLayout().capacity;
        beginVisibleLoad();
    }
    window.Repaint();
}

function startEngine() {
    switchingRenderer = true;
    if (loadTimer) {
        clearTimeout(loadTimer);
        loadTimer = 0;
    }
    ++mainLoadToken;
    if (worker) worker.terminate();
    worker = null;

    albums = [];
    images = [];
    loadState = { requestId: 0, count: 0, completed: 0, loaded: 0, firstDisplayElapsed: 0, loadElapsed: 0, receiveElapsed: 0, displayElapsed: 0, paintCount: 0, paintElapsed: 0, started: 0, active: false };

    try {
        window.DrawMode = drawMode;
        createFonts();
    } finally {
        switchingRenderer = false;
    }

    if (engine === 'worker') {
        const currentWorker = new Worker(workerSource, 'playlist-album-gallery');
        worker = currentWorker;

        currentWorker.onmessage = function (event) {
            if (worker !== currentWorker) return;
            const message = event.data;
            if (message.generation !== generation) return;

            if (message.type === 'catalog') {
                playlistName = message.playlistName || playlistName;
                albums = message.albums || [];
                lastCapacity = getLayout().capacity;
                beginVisibleLoad();
                window.Repaint();
                return;
            }

            if (message.type === 'art') {
                if (message.requestId !== loadState.requestId) return;
                if (message.index < 0 || message.index >= images.length) return;

                ++loadState.completed;
                if (loadState.completed >= loadState.count && loadState.receiveElapsed <= 0 && loadState.started > 0) {
                    loadState.receiveElapsed = performance.now() - loadState.started;
                }
                if (message.image) {
                    images[message.index] = message.image;
                    ++loadState.loaded;
                }
                loadState.loadElapsed = message.elapsed || loadState.loadElapsed;

                const rect = tileRect(message.index, getLayout());
                window.RepaintRect(rect.x, rect.y, rect.w, rect.h);
                window.RepaintRect(MARGIN, 32, Math.max(1, window.Width - 220), 20);
                return;
            }

            if (message.type === 'complete' && message.requestId === loadState.requestId) {
                loadState.active = false;
                loadState.loadElapsed = message.elapsed || loadState.loadElapsed;
                window.RepaintRect(MARGIN, 32, Math.max(1, window.Width - 220), 20);
            }
        };

        currentWorker.onerror = function (event) {
            if (worker === currentWorker) console.log('Worker error: ' + event.message);
        };
    }

    sendPlaylist();
}

function on_size() {
    const capacity = getLayout().capacity;
    if (capacity !== lastCapacity && albums.length) {
        lastCapacity = capacity;
        scheduleVisibleLoad(90);
    }
    window.Repaint();
}

function on_mouse_wheel(step) {
    const next = Math.max(COVER_MIN, Math.min(COVER_MAX,
        coverSize + (step > 0 ? COVER_STEP : -COVER_STEP)));
    if (next === coverSize) return;

    coverSize = next;
    lastCapacity = getLayout().capacity;
    scheduleVisibleLoad();
    window.Repaint();
}

function on_mouse_lbtn_up(x, y) {
    const engineUi = engineRects();
    let nextEngine = engine;
    if (contains(engineUi.main, x, y)) nextEngine = 'main';
    else if (contains(engineUi.worker, x, y)) nextEngine = 'worker';

    if (nextEngine !== engine) {
        engine = nextEngine;
        startEngine();
        return;
    }

    const concurrencyUi = concurrencyRects();
    for (const choice of concurrencyUi.choices) {
        if (!contains(choice.rect, x, y)) continue;
        if (choice.value === concurrency) return;
        concurrency = choice.value;
        scheduleVisibleLoad(0);
        window.Repaint();
        return;
    }

    const rects = rendererRects();
    let nextMode = drawMode;
    if (contains(rects.gdi, x, y)) {
        nextMode = 0;
    } else if (contains(rects.d2d, x, y)) {
        nextMode = 1;
    } else {
        return;
    }

    if (nextMode === drawMode) return;
    drawMode = nextMode;
    startEngine();
}

function on_playlist_switch() {
    sendPlaylist();
}

function on_playlists_changed() {
    sendPlaylist();
}

function on_playlist_items_added(playlistIndex) {
    if (playlistIndex === plman.ActivePlaylist) sendPlaylist();
}

function on_playlist_items_removed(playlistIndex) {
    if (playlistIndex === plman.ActivePlaylist) sendPlaylist();
}

function on_playlist_items_reordered(playlistIndex) {
    if (playlistIndex === plman.ActivePlaylist) sendPlaylist();
}

function on_paint(gr) {
    const trackPaint = loadState.count > 0 && loadState.started > 0 && loadState.displayElapsed <= 0;
    const paintStarted = trackPaint ? performance.now() : 0;

    gr.FillSolidRect(0, 0, window.Width, window.Height, 0xFF101318);
    if (switchingRenderer) return;

    const engineUi = engineRects();
    const rects = rendererRects();
    const concurrencyUi = concurrencyRects();
    const titleRight = Math.max(MARGIN + 80, engineUi.label.x - 10);
    const statusRight = Math.max(MARGIN + 80, concurrencyUi.label.x - 10);
    gr.DrawText('Albums', TITLE_FONT, 0xFFE8EDF2,
        MARGIN, 5, 60, 24,
        DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
    gr.DrawText('Mouse wheel: resize covers', INFO_FONT, 0xFF7E8A96,
        MARGIN + 66, 5, Math.max(1, titleRight - (MARGIN + 66)), 24,
        DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);
    gr.DrawText(statusText(), INFO_FONT, 0xFF9AA6B2,
        MARGIN, 34, Math.max(1, statusRight - MARGIN), 20,
        DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);

    gr.DrawText('Engine:', INFO_FONT, 0xFFB7C1CB,
        engineUi.label.x, engineUi.label.y, engineUi.label.w, engineUi.label.h,
        DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
    drawChoice(gr, engineUi.main, 'Main', engine === 'main');
    drawChoice(gr, engineUi.worker, 'Worker', engine === 'worker');

    gr.DrawText('Renderer:', INFO_FONT, 0xFFB7C1CB,
        rects.label.x, rects.label.y, rects.label.w, rects.label.h,
        DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
    drawChoice(gr, rects.gdi, 'GDI', drawMode === 0);
    drawChoice(gr, rects.d2d, 'D2D', drawMode === 1);

    gr.DrawText('Concurrent:', INFO_FONT, 0xFFB7C1CB,
        concurrencyUi.label.x, concurrencyUi.label.y, concurrencyUi.label.w, concurrencyUi.label.h,
        DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
    for (const choice of concurrencyUi.choices) {
        drawChoice(gr, choice.rect, choice.text, choice.value === concurrency);
    }

    if (!albums.length) {
        gr.DrawText('No albums in the active playlist.', INFO_FONT, 0xFF7E8A96,
            MARGIN, HEADER_H + 8, Math.max(1, window.Width - MARGIN * 2), 28,
            DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
        return;
    }

    gr.SetInterpolationMode(2);

    const layout = getLayout();
    const visibleCount = Math.min(albums.length, layout.capacity);
    for (let i = 0; i < visibleCount; ++i) {
        const album = albums[i];
        const rect = tileRect(i, layout);
        const image = i < images.length ? images[i] : null;

        gr.FillSolidRect(rect.x, rect.y, coverSize, coverSize, 0xFF20262E);
        if (image) {
            drawCover(gr, image, rect.x, rect.y, coverSize);
        } else {
            gr.DrawText(loadState.active ? 'Loading...' : 'No artwork', META_FONT, 0xFF72808D,
                rect.x, rect.y, coverSize, coverSize,
                DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
        }

        gr.DrawText(album.title, ALBUM_FONT, 0xFFE8EDF2,
            rect.x, rect.y + coverSize + 2, coverSize, 18,
            DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);

        const meta = album.artist + (album.year ? '  •  ' + album.year : '');
        gr.DrawText(meta, META_FONT, 0xFF8F9BA7,
            rect.x, rect.y + coverSize + 20, coverSize, 16,
            DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);
    }

    if (trackPaint) {
        const paintedAt = performance.now();
        ++loadState.paintCount;
        loadState.paintElapsed += paintedAt - paintStarted;

        if (loadState.firstDisplayElapsed <= 0 && loadState.loaded > 0) {
            loadState.firstDisplayElapsed = paintedAt - loadState.started;
        }

        if (loadState.completed >= loadState.count && loadState.displayElapsed <= 0) {
            loadState.displayElapsed = paintedAt - loadState.started;
            window.RepaintRect(MARGIN, 32, Math.max(1, window.Width - 220), 20);
        }
    }
}

startEngine();