Experiment to rebuild Diffuse using web applets.
at main 3.4 kB view raw
1import { getTransferables } from "@okikio/transferables"; 2 3import type { Track } from "@applets/core/types.js"; 4import type { Item, State } from "./types"; 5import { arrayShuffle, postMessages, provide, transfer } from "@scripts/common.ts"; 6 7//////////////////////////////////////////// 8// SETUP 9//////////////////////////////////////////// 10 11const actions = { 12 add, 13 pool, 14 shift, 15 unshift, 16}; 17 18const { ports, tasks } = provide({ 19 actions, 20 tasks: { ...actions, data }, 21}); 22 23export type Actions = typeof actions; 24export type Tasks = typeof tasks; 25 26//////////////////////////////////////////// 27// STATE 28//////////////////////////////////////////// 29 30const QUEUE_SIZE = 25; 31 32const _internal: Record<string, { pool: Track[] }> = {}; 33const _state: Record<string, State> = {}; 34 35function data(groupId: string) { 36 return state(groupId); 37} 38 39function emptyState(groupId: string): State { 40 return { 41 future: [], 42 now: null, 43 past: [], 44 }; 45} 46 47function notify(groupId: string) { 48 const d = data(groupId); 49 50 postMessages({ 51 data: { 52 type: "data", 53 data: d, 54 groupId, 55 }, 56 ports: ports.applets, 57 transfer: getTransferables(d), 58 }); 59} 60 61function internal(groupId: string) { 62 _internal[groupId] ??= { pool: [] }; 63 return _internal[groupId]; 64} 65 66function state(groupId: string) { 67 _state[groupId] ??= emptyState(groupId); 68 return _state[groupId]; 69} 70 71//////////////////////////////////////////// 72// ACTIONS 73//////////////////////////////////////////// 74 75function add({ groupId, items }: { groupId: string; items: Item[] }) { 76 state(groupId).future = [...state(groupId).future, ...items]; 77 notify(groupId); 78} 79 80function pool({ groupId, tracks }: { groupId: string; tracks: Track[] }) { 81 internal(groupId).pool = tracks; 82 const queue = state(groupId); 83 84 // TODO: If the pool changes, only remove non-existing tracks 85 // instead of resetting the whole future queue. 86 // 87 // What about past queue items? 88 89 queue.future = []; 90 fill(groupId); 91 92 // Automatically insert track if there isn't any 93 if (!queue.now) return shift({ groupId }); 94 else notify(groupId); 95} 96 97function shift({ groupId }: { groupId: string }) { 98 const queue = state(groupId); 99 const now = queue.future[0] ?? null; 100 queue.now = now; 101 102 queue.future = queue.future.slice(1); 103 queue.past = now ? [...queue.past, now] : queue.past; 104 105 fill(groupId); 106} 107 108function unshift({ groupId }: { groupId: string }) { 109 const queue = state(groupId); 110 if (queue.past.length === 0) return; 111 112 const [last] = queue.past.splice(queue.past.length - 1, 1); 113 const now = last ?? null; 114 115 queue.now = now; 116 queue.future = now ? [now, ...queue.future] : queue.future; 117 118 notify(groupId); 119} 120 121// 🛠️ 122 123// TODO: Most likely there's a more performant solution 124function fill(groupId: string) { 125 const queue = state(groupId); 126 if (queue.future.length >= QUEUE_SIZE) return; 127 128 const pool: Track[] = []; 129 130 let past = new Set(queue.past.map((t) => t.id)); 131 let reducedPool = pool; 132 133 internal(groupId).pool.forEach((track: Track) => { 134 if (past.has(track.id)) { 135 past = past.difference(new Set(track.id)); 136 } else { 137 pool.push(track); 138 } 139 }); 140 141 if (reducedPool.length === 0) { 142 reducedPool = internal(groupId).pool; 143 } 144 145 const poolSelection = arrayShuffle(reducedPool).slice(0, QUEUE_SIZE - queue.future.length); 146 add({ groupId, items: poolSelection }); 147}