diff --git a/extensions/alekhyo_0812/HyperStore.js b/extensions/alekhyo_0812/HyperStore.js new file mode 100644 index 0000000000..1c9df4290b --- /dev/null +++ b/extensions/alekhyo_0812/HyperStore.js @@ -0,0 +1,635 @@ +// Name: HyperStore +// ID: hyperstore +// Description: High-performance multidimensional arrays and tensor math. +// By: Alekhyo Biswas +// License: MIT + +/* +MIT License + +Copyright (c) 2026 Alekhyo Biswas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +(function(Scratch) { + 'use strict'; + + class HyperStore { + constructor() { + this.stores = {}; + this.meta = { + version: '1.2.0', + authors: ['alekhyo_0812'], + lastError: 'None', + lastAccessed: '' + }; + } + + getInfo() { + return { + id: 'hyperstore', + name: 'HyperStore', + color1: '#ff4c4c', + color2: '#cc3333', + color3: '#992626', + docsURI: 'https://alekhyo-biswas.github.io/hyperstore-docs/', + blocks: [ + // --- CATEGORY: FOR THE DEVELOPER --- + { + blockType: Scratch.BlockType.LABEL, + text: 'For The Developer' + }, + + { + opcode: 'getMetaInfo', + blockType: Scratch.BlockType.REPORTER, + text: 'extension [FIELD]', + arguments: { + FIELD: { type: Scratch.ArgumentType.STRING, menu: 'metaFields' } + } + }, + { + opcode: 'logStore', + blockType: Scratch.BlockType.COMMAND, + text: 'debug: log [ID] to JS console', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + { + opcode: 'logAll', + blockType: Scratch.BlockType.COMMAND, + text: 'debug: log all stores to console' + }, + { + opcode: 'getLastError', + blockType: Scratch.BlockType.REPORTER, + text: 'debug: last error message' + }, + { + opcode: 'clearError', + blockType: Scratch.BlockType.COMMAND, + text: 'debug: clear error log' + }, + { + opcode: 'getStoreCount', + blockType: Scratch.BlockType.REPORTER, + text: 'debug: active store count' + }, + { + opcode: 'getTotalMemory', + blockType: Scratch.BlockType.REPORTER, + text: 'debug: approx memory usage (items)' + }, + { + opcode: 'getLastAccessed', + blockType: Scratch.BlockType.REPORTER, + text: 'debug: last accessed ID' + }, + { + opcode: 'getStoreType', + blockType: Scratch.BlockType.REPORTER, + text: 'debug: type of [ID] (Fixed/Dynamic)', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + { + opcode: 'isStoreEmpty', + blockType: Scratch.BlockType.BOOLEAN, + text: 'debug: is [ID] empty/zeroed?', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + { + opcode: 'pruneStores', + blockType: Scratch.BlockType.COMMAND, + text: 'debug: delete all empty stores' + }, + { + opcode: 'setSafeMode', + blockType: Scratch.BlockType.COMMAND, + text: 'debug: set safe mode [BOOL]', + arguments: { BOOL: { type: Scratch.ArgumentType.STRING, menu: 'booleans' } } + }, + { + opcode: 'simulateLag', + blockType: Scratch.BlockType.COMMAND, + text: 'debug: simulate lag [MS]ms', + arguments: { MS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } } + }, + { + opcode: 'getDevelopers', + blockType: Scratch.BlockType.REPORTER, + text: 'list of developers (JSON)' + }, + + // --- CATEGORY: MANAGEMENT --- + { + blockType: Scratch.BlockType.LABEL, + text: 'Storage Management' + }, + { + opcode: 'createFixed', + blockType: Scratch.BlockType.COMMAND, + text: 'create fixed [ID] shape [SHAPE] (Clipped)', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + SHAPE: { type: Scratch.ArgumentType.STRING, defaultValue: '10,10' } + } + }, + { + opcode: 'createDynamic', + blockType: Scratch.BlockType.COMMAND, + text: 'create dynamic [ID] with [DIMS] dimensions (Infinite)', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + DIMS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 } + } + }, + { + opcode: 'deleteStore', + blockType: Scratch.BlockType.COMMAND, + text: 'delete storage [ID]', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + { + opcode: 'deleteAll', + blockType: Scratch.BlockType.COMMAND, + text: 'delete all storage' + }, + { + opcode: 'storeExists', + blockType: Scratch.BlockType.BOOLEAN, + text: 'storage [ID] exists?', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + { + opcode: 'getShape', + blockType: Scratch.BlockType.REPORTER, + text: 'shape of [ID]', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + + // --- CATEGORY: DATA ACCESS --- + { + blockType: Scratch.BlockType.LABEL, + text: 'Data Access' + }, + { + opcode: 'setValue', + blockType: Scratch.BlockType.COMMAND, + text: 'set value at [COORDS] in [ID] to [VAL]', + arguments: { + COORDS: { type: Scratch.ArgumentType.STRING, defaultValue: '0,0' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + VAL: { type: Scratch.ArgumentType.STRING, defaultValue: 'hello' } + } + }, + { + opcode: 'getValue', + blockType: Scratch.BlockType.REPORTER, + text: 'get value at [COORDS] from [ID]', + arguments: { + COORDS: { type: Scratch.ArgumentType.STRING, defaultValue: '0,0' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } + } + }, + { + opcode: 'fillStore', + blockType: Scratch.BlockType.COMMAND, + text: 'fill [ID] with [VAL]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + VAL: { type: Scratch.ArgumentType.STRING, defaultValue: '0' } + } + }, + + // --- CATEGORY: STRUCTURE --- + { + blockType: Scratch.BlockType.LABEL, + text: 'Structure & Math' + }, + { + opcode: 'reshapeStore', + blockType: Scratch.BlockType.COMMAND, + text: 'reshape [ID] to [SHAPE]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + SHAPE: { type: Scratch.ArgumentType.STRING, defaultValue: '100,1' } + } + }, + { + opcode: 'flattenStore', + blockType: Scratch.BlockType.COMMAND, + text: 'flatten [ID] into new storage [NEWID]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + NEWID: { type: Scratch.ArgumentType.STRING, defaultValue: 'flat1' } + } + }, + { + opcode: 'applyOp', + blockType: Scratch.BlockType.COMMAND, + text: 'apply [OP] to all in [ID] with value [N]', + arguments: { + OP: { type: Scratch.ArgumentType.STRING, menu: 'mathOps' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + N: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 } + } + }, + + // --- CATEGORY: I/O --- + { + blockType: Scratch.BlockType.LABEL, + text: 'I/O & Lists' + }, + { + opcode: 'exportJSON', + blockType: Scratch.BlockType.REPORTER, + text: 'export [ID] as JSON', + arguments: { ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } } + }, + { + opcode: 'importJSON', + blockType: Scratch.BlockType.COMMAND, + text: 'import JSON [JSON] into [ID]', + arguments: { + JSON: { type: Scratch.ArgumentType.STRING, defaultValue: '{}' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' } + } + }, + { + opcode: 'dumpToList', + blockType: Scratch.BlockType.COMMAND, + text: 'dump [ID] to Scratch List [LIST]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + LIST: { type: Scratch.ArgumentType.STRING, menu: 'projectLists' } + } + }, + { + opcode: 'loadFromList', + blockType: Scratch.BlockType.COMMAND, + text: 'load [ID] from Scratch List [LIST] shape [SHAPE]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'grid1' }, + LIST: { type: Scratch.ArgumentType.STRING, menu: 'projectLists' }, + SHAPE: { type: Scratch.ArgumentType.STRING, defaultValue: '10,10' } + } + } + ], + menus: { + mathOps: { + acceptReporters: true, + items: ['Add', 'Subtract', 'Multiply', 'Divide', 'Modulo', 'Power'] + }, + metaFields: { + acceptReporters: true, + items: ['Version', 'Author', 'Platform'] + }, + booleans: { + acceptReporters: true, + items: ['true', 'false'] + }, + projectLists: { + acceptReporters: true, + items: 'getProjectLists' + } + } + }; + } + + // --- UTILS --- + + _parseShape(str) { return String(str).split(',').map(n => parseInt(n) || 1); } + _parseCoords(str) { return String(str).split(',').map(n => parseInt(n) || 0); } + _setError(msg) { this.meta.lastError = msg; } + + _getFlatIndex(coords, shape) { + let index = 0; + let stride = 1; + for (let i = shape.length - 1; i >= 0; i--) { + const c = coords[i] || 0; + if (c < 0 || c >= shape[i]) return -1; + index += c * stride; + stride *= shape[i]; + } + return index; + } + + _indexToCoords(index, shape) { + let coords = new Array(shape.length); + let s = 1; + let strides = new Array(shape.length); + for (let i = shape.length - 1; i >= 0; i--) { strides[i] = s; s *= shape[i]; } + for (let i = 0; i < shape.length; i++) { + coords[i] = Math.floor(index / strides[i]); + index %= strides[i]; + } + return coords.join(','); + } + + _expandStore(store, targetCoords) { + // Logic to resize a Dynamic store to fit targetCoords + let newShape = [...store.shape]; + let changed = false; + + for(let i=0; i= newShape[i]) { + newShape[i] = targetCoords[i] + 1; // +1 to fit index + changed = true; + } + } + + if (!changed) return; + + // Perform Resize + const newSize = newShape.reduce((a, b) => a * b, 1); + const newData = new Array(newSize).fill(0); + + // We must copy data. To do this generally for N-dims is complex. + // Simplified approach: Flatten old, calculate coords, place in new. + // For performance, this is heavy, but "Superior Control" requires it. + + // 1. Iterate old data + for(let i=0; i v.type === 'list'); + for (const list of targetLists) { + lists.push({ + text: `${target.isStage ? 'Stage' : target.getName()}: ${list.name}`, + value: list.name // We use name to look it up later + }); + } + } + } + return lists.length > 0 ? lists : [{text: '(No Lists Found)', value: ''}]; + } + + + + // --- DEVELOPER BLOCKS --- + + getMetaInfo(args) { + if (args.FIELD === 'Version') return this.meta.version; + if (args.FIELD === 'Author') return this.meta.authors.join(', '); + if (args.FIELD === 'Platform') return 'TurboWarp/JS'; + return ''; + } + + logStore(args) { + const s = this.stores[args.ID]; + console.log(`[HyperStore Debug] ${args.ID}:`, s ? s : 'Not Found'); + } + + logAll() { console.log('[HyperStore Dump]', this.stores); } + getLastError() { return this.meta.lastError; } + clearError() { this.meta.lastError = 'None'; } + getStoreCount() { return Object.keys(this.stores).length; } + + getTotalMemory() { + let count = 0; + Object.values(this.stores).forEach(s => count += s.data.length); + return count; + } + + getLastAccessed() { return this.meta.lastAccessed; } + + getStoreType(args) { + const s = this.stores[args.ID]; + if (!s) return 'None'; + return s.mode === 'dynamic' ? 'Dynamic' : 'Fixed'; + } + + isStoreEmpty(args) { + const s = this.stores[args.ID]; + if (!s) return true; + return s.data.every(v => v == 0); + } + + pruneStores() { + for (const key in this.stores) { + if (this.stores[key].data.every(v => v == 0)) { + delete this.stores[key]; + } + } + } + + setSafeMode(args) { this.meta.safeMode = (args.BOOL === 'true'); } + + simulateLag(args) { + const start = Date.now(); + while (Date.now() - start < args.MS) {} + } + + getDevelopers() { return JSON.stringify(this.meta.authors); } + + // --- CORE IMPL --- + + createFixed(args) { + const shape = this._parseShape(args.SHAPE); + const size = shape.reduce((a, b) => a * b, 1); + this.stores[args.ID] = { + mode: 'fixed', + shape: shape, + data: new Array(size).fill(0) + }; + this.meta.lastAccessed = args.ID; + } + + createDynamic(args) { + const dims = parseInt(args.DIMS) || 2; + const initialShape = new Array(dims).fill(1); + this.stores[args.ID] = { + mode: 'dynamic', + shape: initialShape, // Starts tiny + data: [0] + }; + this.meta.lastAccessed = args.ID; + } + + deleteStore(args) { delete this.stores[args.ID]; } + deleteAll() { this.stores = {}; } + storeExists(args) { return !!this.stores[args.ID]; } + + getShape(args) { + const s = this.stores[args.ID]; + this.meta.lastAccessed = args.ID; + return s ? s.shape.join(',') : ''; + } + + setValue(args) { + const id = args.ID; + const s = this.stores[id]; + this.meta.lastAccessed = id; + if (!s) { + this._setError(`Store ${id} not found`); + return; + } + + const coords = this._parseCoords(args.COORDS); + + // Validation for dimension count + if (coords.length !== s.shape.length) { + this._setError(`Dimension mismatch. Store is ${s.shape.length}D, coords are ${coords.length}D`); + return; + } + + // Fixed Mode: Check Bounds + if (s.mode === 'fixed') { + const idx = this._getFlatIndex(coords, s.shape); + if (idx === -1) return; // Clipped/Ignored + s.data[idx] = args.VAL; + } + // Dynamic Mode: Expand + else if (s.mode === 'dynamic') { + // Check if expansion needed + let needsExpansion = false; + for(let i=0; i= s.shape[i]) needsExpansion = true; + } + + if (needsExpansion) { + this._expandStore(s, coords); + } + + const idx = this._getFlatIndex(coords, s.shape); + s.data[idx] = args.VAL; + } + } + + getValue(args) { + const s = this.stores[args.ID]; + this.meta.lastAccessed = args.ID; + if (!s) return ''; + + const coords = this._parseCoords(args.COORDS); + const idx = this._getFlatIndex(coords, s.shape); + + if (idx === -1 || idx >= s.data.length) return ''; + return s.data[idx]; + } + + fillStore(args) { + const s = this.stores[args.ID]; + if (s) s.data.fill(args.VAL); + } + + reshapeStore(args) { + const s = this.stores[args.ID]; + if (!s) return; + const newShape = this._parseShape(args.SHAPE); + const newSize = newShape.reduce((a, b) => a * b, 1); + if (newSize === s.data.length) s.shape = newShape; + else this._setError('Reshape failed: Total element count must match'); + } + + flattenStore(args) { + const s = this.stores[args.ID]; + if (!s) return; + this.stores[args.NEWID] = { + mode: 'fixed', + shape: [s.data.length], + data: [...s.data] + }; + } + + applyOp(args) { + const s = this.stores[args.ID]; + if (!s) return; + const n = Number(args.N); + for (let i = 0; i < s.data.length; i++) { + let v = Number(s.data[i]); + if (args.OP === 'Add') v += n; + else if (args.OP === 'Subtract') v -= n; + else if (args.OP === 'Multiply') v *= n; + else if (args.OP === 'Divide') v /= n; + else if (args.OP === 'Modulo') v %= n; + else if (args.OP === 'Power') v = Math.pow(v, n); + s.data[i] = v; + } + } + + // --- I/O IMPL --- + + exportJSON(args) { + const s = this.stores[args.ID]; + return s ? JSON.stringify(s) : '{}'; + } + + importJSON(args) { + try { + const obj = JSON.parse(args.JSON); + if (obj.shape && obj.data) this.stores[args.ID] = obj; + } catch (e) { + this._setError('JSON Import Failed'); + } + } + + dumpToList(args, util) { + const s = this.stores[args.ID]; + if (!s) return; + const listVar = util.target.lookupVariableByNameAndType(args.LIST, 'list'); + if (listVar) { + listVar.value = [...s.data]; + } else { + this._setError('Target List not found'); + } + } + + loadFromList(args, util) { + const listVar = util.target.lookupVariableByNameAndType(args.LIST, 'list'); + if (!listVar) { + this._setError('Target List not found'); + return; + } + const shape = this._parseShape(args.SHAPE); + const size = shape.reduce((a, b) => a * b, 1); + + // Auto-pad or clip to fit strict shape + const newData = new Array(size).fill(0); + for(let i=0; i