diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 5f93641..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-# To get started with Dependabot version updates, you'll need to specify which
-# package ecosystems to update and where the package manifests are located.
-# Please see the documentation for all configuration options:
-# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
-
-version: 2
-updates:
- - package-ecosystem: "npm" # See documentation for possible values
- directory: "/" # Location of package manifests
- schedule:
- interval: "weekly"
- open-pull-requests-limit: 0
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index c18dd8d..0000000
--- a/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-__pycache__/
diff --git a/App.js b/App.js
deleted file mode 100644
index ea88c94..0000000
--- a/App.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { DataTypes } from "./Buffifier.js";
-
-export class App {
-
- static _meta = {
- get props() {
-
- return {
- mouseX: DataTypes.Uint16,
- mouseY: DataTypes.Uint16,
-
- mouseDown: DataTypes.Uint8,
-
- mouseWheelDeltaY: DataTypes.Int16,
-
- canvasWidth: DataTypes.Uint16,
- canvasHeight: DataTypes.Uint16,
-
- mouseOverEntityIndices: DataTypes.BufferedArray,
- world: DataTypes.World
- }
-
- }
-
- };
-
-}
diff --git a/Buffifier.js b/Buffifier.js
index ad34f33..ec5054d 100644
--- a/Buffifier.js
+++ b/Buffifier.js
@@ -1,1073 +1,650 @@
-
-import { MainThread } from "./threads/Main.js";
-import { RendererThread } from "./threads/Renderer.js";
-import { EntityThread } from "./threads/Entity.js";
-import { TerrainThread } from "./threads/Terrain.js";
-import { PathingThread } from "./threads/Pathing.js";
-
-
-import { App } from "./App.js";
-import { World } from "./World.js";
-import { Entity } from "./Entity.js";
-
-
+const isMainThread = typeof WorkerGlobalScope === 'undefined'
+const threadName = isMainThread ? 'main' : location.search.slice(1)
const config = {
- classes: [
- App,
- World,
- Entity
- ],
- threads: {
- main: [
- // only one main thread class currently supported
- MainThread
- ],
- canvas: [
- // only one canvas thread class currently supported
- RendererThread
- ],
- others: [
- EntityThread,
- TerrainThread,
- PathingThread
- ]
- }
-};
-
-export const DataTypes = {
-
- null: 0,
- boolean: 1,
- Int8: 2,
- Uint8: 3,
- Int16: 4,
- Uint16: 5,
- Int32: 6,
- Uint32: 7,
- Float32: 8,
- Float64: 9,
- BigInt64: 10,
- BigUint64: 11,
- BufferedArray: 12
- // classes from config automatically added here
-
-}
-
-
-
-export class BufferedArray {
-
- constructor() {
-
- Object.defineProperty(this, "_a", {
- value: {},
- writable: false,
- enumerable: false,
- configurable: false
- });
-
- }
-
- static _meta = {
- get props() {
- return {
- of: DataTypes.Uint8,
- length: DataTypes.Uint32
- }
- }
- };
-
- getReferenceType(index) {
- return MemorySystem.getReferenceType(this._a.ofType, this._a.typedZeroIndex + index);
- }
-
- getPrimitiveType(index) {
- return MemorySystem.getPrimitiveType(this._a.ofType, this._a.typedZeroIndex + index);
- }
-
- setReferenceType(index, v) {
- return MemorySystem.setReferenceType(v, this._a.ofType, this._a.typedZeroIndex + index);
- }
-
- setPrimitiveType(index, v) {
- return MemorySystem.setPrimitiveType(v, this._a.ofType, this._a.typedZeroIndex + index);
- }
-
- [Symbol.iterator]() {
-
- let index = 0;
-
- return {
- next: () => {
- if (index < this.length) {
- return {
- value: this.at(index++),
- done: false
- };
- } else {
- return {
- done: true
- };
- }
- },
- };
- }
-
- onCreatedInstance() {
-
-
- const ofProp = this._b.meta.propsComputed.of;
-
- const ofTypedArrayIndex = (this._b.index + ofProp.offset) / ofProp.type.bytes;
-
- const ofVal = Atomics.load(ofProp.type.typedArray, ofTypedArrayIndex);
-
- this._a.ofType = MemorySystem.cache.typesLookup[ofVal];
-
- const currentWorkingSpaceIndex = this._b.index + this._b.meta.allocate;
-
- const remainder = (this._b.index + this._b.meta.allocate) % this._a.ofType.bytes;
-
- const additionalOffset = remainder === 0 ? remainder : (this._a.ofType.bytes - remainder);
-
- const workingSpaceIndex = currentWorkingSpaceIndex + additionalOffset;
-
- this._a.typedZeroIndex = workingSpaceIndex / this._a.ofType.bytes;
-
- const lengthProp = this._b.meta.propsComputed.length;
-
- this._a.lengthType = lengthProp.type;
-
- this._a.lengthTypedArrayIndex = (this._b.index + lengthProp.offset) / lengthProp.type.bytes;
-
- if(this._a.ofType.referenceType) {
- this.at = this.getReferenceType;
- this.set = this.setReferenceType;
- } else {
- this.at = this.getPrimitiveType;
- this.set = this.setPrimitiveType;
- }
- }
-
- at = () => undefined;
-
- set = () => undefined;
-
- push(v) {
-
-
- const length = Atomics.add(this._a.lengthType.typedArray, this._a.lengthTypedArrayIndex, 1) + 1;
-
- MemorySystem.setRaw(v, this._a.ofType, (this._a.typedZeroIndex + length - 1))
-
- return length;
-
- }
-
+ allocate: 100000000 // 100 MB (is 8 bytes aligned)
}
-class DataType {
-
- typeByte = null;
-
- bytes = null;
-
- referenceType = null;
-
- typedArray = null;
-
- setTransform = (n) => {
- return n;
- }
-
- getTransform = (n) => {
- return n;
- }
- constructor(typeByte) {
+const storeTypes = Object.freeze({
+ reference: 0,
+ value: 1
+})
- this.typeByte = typeByte;
+const zones = Object.freeze({
+ a: 0,
+ b: 1
+})
- this.referenceType = typeByte > 11;
- // set how many bytes needed to store the data type
-
- if((typeByte > 5 && typeByte < 9) || typeByte > 11) {
- this.bytes = 4;
- } else if(typeByte === 4 || typeByte === 5) {
- this.bytes = 2;
- } else if(typeByte < 4) {
- this.bytes = 1;
- } else if(typeByte > 8 && typeByte < 12) {
- this.bytes = 8;
- }
-
- // set the typed array used for storing the data type
- if(typeByte === 0) {
- this.typedArray = MemorySystem.uint8Array;
- } else if(typeByte === 1) {
- this.typedArray = MemorySystem.uint8Array;
- this.getTransform = TypeConverter.intToBool;
- this.setTransform = TypeConverter.boolToInt;
- } else if(typeByte === 2) {
- this.typedArray = MemorySystem.int8Array;
- } else if(typeByte === 3) {
- this.typedArray = MemorySystem.uint8Array;
- } else if(typeByte === 4) {
- this.typedArray = MemorySystem.int16Array;
- } else if(typeByte === 5) {
- this.typedArray = MemorySystem.uint16Array;
- } else if(typeByte === 6) {
- this.typedArray = MemorySystem.int32Array;
- } else if(typeByte === 7) {
- this.typedArray = MemorySystem.uint32Array;
- } else if(typeByte === 8) {
- // float32 goes in uint32
- this.typedArray = MemorySystem.uint32Array;
- this.getTransform = TypeConverter.uint32ToFloat32;
- this.setTransform = TypeConverter.float32ToUint32;
- } else if(typeByte === 9) {
- // float64 goes in biguint64
- this.typedArray = MemorySystem.biguint64Array;
- this.getTransform = TypeConverter.uint64ToFloat64;
- this.setTransform = TypeConverter.float64ToUint64;
- } else if(typeByte === 10) {
- this.typedArray = MemorySystem.bigint64Array;
- } else if(typeByte === 11) {
- this.typedArray = MemorySystem.biguint64Array;
- } else if(typeByte > 11) {
- // reference objects are uint32 because it holds
- // the index of the object in the buffer
- this.typedArray = MemorySystem.uint32Array;
- }
- }
-}
-
-class TypeConverter {
-
- static buffer = new ArrayBuffer(8);
-
- static float32Arr = new Float32Array(this.buffer);
- static float64Arr = new Float64Array(this.buffer);
-
- static uint32Array = new Uint32Array(this.buffer);
- static uint64Array = new BigUint64Array(this.buffer);
-
- static float32ToUint32 = (n) => {
- this.float32Arr[0] = n;
- return this.uint32Array[0];
- }
-
- static uint32ToFloat32 = (n) => {
- this.uint32Array[0] = n;
- return this.float32Arr[0];
- }
-
- static float64ToUint64 = (n) => {
- this.float64Arr[0] = n;
- return this.uint64Array[0];
- }
-
- static uint64ToFloat64 = (n) => {
- this.uint64Array[0] = n;
- return this.float64Arr[0];
- }
-
- static boolToInt(b) {
- if(b) {
- return 1;
- } else {
- return 0;
- }
- }
-
- static intToBool(n) {
- return n !== 0;
- }
-
-}
-
-
-class MemorySystem {
-
- static buffer = null;
-
- static rootObject = null;
-
- static int8Array = null;
- static uint8Array = null;
-
- static int16Array = null;
- static uint16Array = null;
-
- static int32Array = null;
- static uint32Array = null;
-
- static bigint64Array = null;
- static biguint64Array = null;
-
- static metaDataIndices = {
- freeIndex: 0, // uint32
- bytesAllocated: 1, // uint32
- workersSignal: 2, // int32
- };
+export class Buffifier {
+ static buffer = null
- static objectMetaDataIndicesOffset = {
- lock: 0, // int32
- type: 4, // uint8
- workingSpaceBytes: 2 // uint32
- };
+ static workers = new Map()
- static cache = {
- typesLookup: null,
- classLookup: null,
- instanceLookup: null
- };
+ // #region Typed Arrays
- static threadMetaDataBytes = 8;
+ static typedArrays = {
+ int8: null,
+ uint8: null,
- // [0-3] int32 locking/unlocking object
- // [4] uint8 type byte
- // [8-11] uint32 working space amount for variable sized objects (e.g., BufferedArray)
- static objectMetaDataBytes = 12;
+ int16: null,
+ uint16: null,
- // must be a multiple of 8
- // [0-3] uint32 starting index of free space
- // [4-7] uint32 of total memory allocated in bytes
- // [8-11] int32 signal for workers
- static metaDataBytes = 16;
+ int32: null,
+ uint32: null,
- static rootObjectIndex = null;
+ int64: null,
+ uint64: null
+ }
- static init(buffer) {
- // buffer is always a SharedArrayBuffer
- this.buffer = buffer;
+ // #endregion
- // create all typed arrays with the same underlying buffer
+ // #region Buffer Header Offsets
- this.int8Array = new Int8Array(this.buffer);
- this.uint8Array = new Uint8Array(this.buffer);
+ /*
+ BUFFER HEADER:
+ (64 bit aligned)
+ uint32 zoneA search start index for free block
+ uint32 zoneB search start index for free block
+ */
+ static bufferHeaderOffset = Object.freeze({
+ zoneASearch: 0, // uint32
+ zoneBSearch: 1, // uint32
+ })
- this.int16Array = new Int16Array(this.buffer);
- this.uint16Array = new Uint16Array(this.buffer);
+ // #endregion
- this.int32Array = new Int32Array(this.buffer);
- this.uint32Array = new Uint32Array(this.buffer);
+ // #region Object Header Offsets
+
+ // OBJECT HEADER: int32: allocated/unallocated, uint16: generation, uint16: type,
+ // (64 bit aligned)
+ static objectHeaderOffset = Object.freeze({
+ occupied: 0, // int32
+ generation: 1, // uint16
+ type: 2 // uint16
+ })
- this.bigint64Array = new BigInt64Array(this.buffer);
- this.biguint64Array = new BigUint64Array(this.buffer);
+ // #endregion
- // concat all classes to one array for processing...
- const classesToProcess = config.classes.slice();
+ // int32 index that starts the actual data
+ static zoneAStartIndex = null
+ static zoneBStartIndex = null
- classesToProcess.push(
- BufferedArray,
- ...config.threads.main,
- ...config.threads.canvas,
- ...config.threads.others
- );
- classesToProcess.sort((a, b) => {
- if (a.name < b.name) { return -1; }
- if (a.name > b.name) { return 1; }
- throw new Error("Expected unique classes.");
- });
+ static zoneABlockSizeInt32 = 16250
+ static zoneBBlockSizeInt32 = 65000
- console.log(classesToProcess);
- let dataTypesCount = Object.keys(DataTypes).length;
+ /*
+ NOTES:
+ - buffer is structured like this:
+ - Biggest objects are allocated at the right of the buffer at a fixed size starting at the very end and moving left...
+ - Second biggest items define the block sizes that are allocated starting from the left
+ */
- // add custom classes to DataTypes
- for (const cls of classesToProcess) {
- DataTypes[cls.name] = dataTypesCount;
- dataTypesCount++;
- }
+ static buffify(o) {
+ console.log(`[${threadName}] Buffifier.buffify()`, this.name, o)
- // stores DataType instances in an array indexed by type byte
- this.cache.typesLookup = new Array(dataTypesCount);
+ // basically take `o` and record: prop bytes, prop offsets, total bytes.
- // stores classes so instances can be created when just having the type byte
- this.cache.classLookup = new Array(dataTypesCount);
+ /*
- // stores instances of objects that are backed by the buffer...
- // ... indexed by the objects location (index) in the buffer
- this.cache.instanceLookup = new Map(); // new Array(this.buffer.byteLength);
+ After all classes are buffified, we have details for all types
+
+ buffifer.init will:
+ - Determine the largest object and make that go into zone B
+ - Transform the types to an array to get a number for a type id.
+
+ */
- // sorting the entries by value because distrustful of consistent ordering
- const sortedDataTypes = Object.entries(DataTypes).sort((a, b) => {
- if (a[1] < b[1]) { return -1; }
- if (a[1] > b[1]) { return 1; }
- throw new Error("Expected unique values on sort.");
- });
- for (const [k, v] of sortedDataTypes) {
- this.cache.typesLookup[v] = new DataType(v, k);
+ //const classes = {
+ /*
+ 'App': {
+ cls: App,
+ zone: 0,
+ props: {
+ prop1: {},
+ prop2: {}
+ }
}
- for (const cls of classesToProcess) {
-
- const typeByte = DataTypes[cls.name];
-
- this.cache.classLookup[typeByte] = cls;
-
- const meta = cls._meta;
-
- meta.type = this.cache.typesLookup[typeByte];
-
- // allocate gets incremented later
- meta.allocate = this.objectMetaDataBytes;
+ */
+ //}
- // stores prop names
- meta.propsLookup = [];
+ // typesArray.push(this)
- // stores details about each prop
- meta.propsComputed = {};
+ // OBJECT DATA: type, block
- // sorts props by prop name so order is consistant
- const sortedProps = Object.entries(meta.props).sort((a, b) => {
- if (a[0] < b[0]) { return -1; }
- if (a[0] > b[0]) { return 1; }
- throw new Error("Expected unique prop names on sort.");
- });
+ // on buffify, just register object type and do setters/getters of props
- for(const [k, v] of sortedProps) {
+ // OBJECT GETTER NEEDS: zone, typedArray, block, typedOffset, getTransform
+ // OBJECT SETTER NEEDS: zone, typedArray, block, typedOffset, setTransform
+ //
+ // - block is known on construction
- const type = this.cache.typesLookup[v];
-
- // needed so the prop is aligned to fit in its corresponding typed array
- const additionalOffset = this.computeAdditionalOffset(meta.allocate, type);
+ // get all the props and info: typedArray, typedArrayOffset...
+ // - when accessing a prop:
+ // - get value (v) directly from typedArray on typedArrayOffset (including through getter)
+ // - if it's a primitive value type:
+ // - just return v
+ // - if it's an object value type:
+ // - just return `new cls(v)`
+ // - if it's an object reference:
+ // - unpack to get block index and generation number
- meta.propsComputed[k] = {
- offset: meta.allocate + additionalOffset,
- type: type
- };
-
- meta.allocate += additionalOffset + type.bytes;
-
- meta.propsLookup.push(k);
-
- }
-
- }
-
- // rootObjectIndex is the first byte after meta data and thread meta data
- this.rootObjectIndex = this.metaDataBytes + (ThreadSystem.totalThreads * this.threadMetaDataBytes);
-
- if(Buffifier.isMainThread) {
-
- // explicit here for clarity
- const freeIndex = this.rootObjectIndex;
-
- // Buffifier.rootObject hasn't been created yet, but set the
- // freeIndex to the rootObjectIndex because the next
- // memory object created will be the root object
- Atomics.store(this.uint32Array, this.metaDataIndices.freeIndex, freeIndex);
-
- // store how much buffer space we allocated for potential future use
- Atomics.store(this.uint32Array, this.metaDataIndices.bytesAllocated, this.buffer.byteLength);
+ const name = this.name
+ /*
+ 'App': {
+ cls: App,
+ zone: 0,
+ props: {
+ prop1: {},
+ prop2: {}
+ }
}
- }
-
- static createInstance(cls, values, options) {
-
- const workingOptions = Object.assign({
- workingSpace: 0
- }, options);
+ */
+
- const meta = cls._meta;
+ const sortedProps = (Array.isArray(o) ? o : o.props).toSorted()
- const objectBlockIndex = this.reserveFreeBlock(meta.allocate + workingOptions.workingSpace);
+ const props = {}
- // since no thread at this point knows about objectBlockIndex except right here...
- // ... so we should be good to write at that location without issue
+ // TODO: Maybe not hard code 8
+ let bytesOffset = 8
- // objectBlockIndex is a multiple of 4 so hoping dividing by 4 gives an integer
+ for (const [propName, propType] of sortedProps) {
- this.int32Array[(objectBlockIndex / 4) + this.objectMetaDataIndicesOffset.lock] = 1; // mark as unlocked
+ // Assume object
+ let bytes = 4
+ let name = propType.name
+ let primitive = false
- this.uint8Array[objectBlockIndex + this.objectMetaDataIndicesOffset.type] = meta.type.typeByte; // add type byte
+ if(typeof propType === 'string') {
+ // value
+ bytes = types[propType].bytes
+ name = propType
+ primitive = true
+ }
+
+ // check if bytesOffset is aligned with current type...
+ // ... adjust bytesOffset if not.
- this.uint32Array[(objectBlockIndex / 4) + this.objectMetaDataIndicesOffset.workingSpaceBytes ] = workingOptions.workingSpace; // add working space size
+ bytesOffset += Buffifier.computeAdditionalOffset(bytesOffset, bytes)
- // create vanilla instance
- const instance = new cls();
+ // Assume at this point bytesOffset is aligned for the current type ...
- this.configureInstance(instance, objectBlockIndex, meta, workingOptions);
+ // if it's 2 bytes type... to get the int8 offset starting at first int8 of block index...
+ // ... [0,1,2,3,4,5,6,7]
+ // ... [0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ]
+ // ... [0 ,1 ,2 ,3 ,4 ,5 ]
- if(!!values) {
- // set any initial values
- for (const [key, val] of Object.entries(values)) {
- // prop can, but doesn't have to, point to a memory value
- instance[key] = val;
- }
- }
+ // Do we just do bytesOffset / propTypeBytes ... 8 / 2 = 4
- if("onCreatedInstance" in instance) {
- instance.onCreatedInstance();
- }
+ // So to get the propOffsetIndex, we do bytesOffset / propTypeBytes ...
+ const offset = bytesOffset / bytes
- return instance;
+ // Then we increment bytesOffset by propTypeBytes
+ bytesOffset += bytes
- }
+ props[propName] = {
+ name,
+ offset,
+ bytes,
+ primitive
+ }
- static configureInstance(instance, index, meta, options) {
-
- // construct helper object...
- const _b = {
- index: index,
- lastByteIndex: index + meta.allocate + options.workingSpace - 1,
- blockSize: meta.allocate + options.workingSpace,
- meta: meta,
- options: options//,
- //lock: this.lockObject.bind(instance),
- //unlock: this.unlockObject.bind(instance),
- };
-
- Object.defineProperty(instance, "_b", {
- value: _b,
- writable: false,
- enumerable: false,
- configurable: false
- });
-
- for(const propName of meta.propsLookup){
- this.defineObjectProp(instance, propName);
- }
}
- // rounds up so the type can be accessed with its corresponding typed array
- static computeAdditionalOffset(currentBytesOffset, type) {
+ let totalBytes = Buffifier.computeAdditionalOffset(bytesOffset, 8)
- const remainder = currentBytesOffset % type.bytes;
- return remainder === 0 ? remainder : (type.bytes - remainder);
+ classes[name] = {
+ cls: this,
+ totalBytes,
+ zone: zones.a,
+ props
}
- static reserveFreeBlock(allocate) {
- // Round up 'allocate' so it's a multiple of 8.
- // This is needed so we can properly set the offsets for the object properties.
- // We can now assume that all objects will start at an index that is a multiple of 8.
- const remainder = allocate % 8;
-
- // Atomics.add() returns the old value before addition.
- // That value is the next free block index.
- return Atomics.add(this.uint32Array, 0, (allocate + (8 - remainder)));
+ // prob do this last for safety
+ /*
+ for (const method of ['valueOf', 'toString', 'toJSON']) {
+ // adds it as an instance prop
+ Object.defineProperty(this.prototype, method, {
+ value() {
+ // ... return the packed value of the all prop values
+ },
+ writable: true,
+ configurable: true,
+ enumerable: false
+ })
}
+ */
+ }
- static newInstanceFromIndex(index) {
-
- // do we even need atomics here?
- // const typeByte = Atomics.load(this.uint8Array, index + 4);
- const typeByte = this.uint8Array[index + 4];
-
- // also do we need atomics here because working space should only be written once on object creation
- // const workingSpace = Atomics.load(this.uint32Array, (index + 8) / 4);
- const workingSpace = this.uint32Array[(index + 8) / 4];
-
- const cls = this.cache.classLookup[typeByte];
-
- const instance = new cls();
-
- this.configureInstance(instance, index, cls._meta, {
- workingSpace: workingSpace
- });
+ // (9, 8) = 7, (0, 4) = 0, (1, 4) = 3
+ static computeAdditionalOffset(currentBytesOffset, bytes) {
+ const remainder = currentBytesOffset % bytes
- if("onCreatedInstance" in instance) {
- instance.onCreatedInstance();
- }
-
- return instance;
+ return remainder === 0 ? 0 : bytes - remainder
+ }
+ static initMemory = (root, sab) => {
+ if (sab === undefined) {
+ this.buffer = new SharedArrayBuffer(config.allocate)
+ } else {
+ this.buffer = sab
}
- static getInstance(index) {
-
- if(this.cache.instanceLookup.has(index)) {
+
- return this.cache.instanceLookup.get(index);
-
- } else {
- const o = this.newInstanceFromIndex(index);
- this.cache.instanceLookup.set(index, o);
- return o;
- }
+ this.typedArrays.int8 = new Int32Array(this.buffer)
+ this.typedArrays.uint8 = new Uint32Array(this.buffer)
- //if(this.cache.instanceLookup[index] === undefined) {
- // this.cache.instanceLookup[index] = this.newInstanceFromIndex(index);
- //}
+ this.typedArrays.int16 = new Int16Array(this.buffer)
+ this.typedArrays.uint16 = new Uint16Array(this.buffer)
- //return this.cache.instanceLookup[index];
+ this.typedArrays.int32 = new Int32Array(this.buffer)
+ this.typedArrays.uint32 = new Uint32Array(this.buffer)
- }
+ this.typedArrays.int64 = new BigInt64Array(this.buffer)
+ this.typedArrays.uint64 = new BigUint64Array(this.buffer)
- static getReferenceType(type, typedArrayValueIndex) {
- const index = Atomics.load(type.typedArray, typedArrayValueIndex);
- return (index === 0 ? null : this.getInstance(index));
- }
+ this.zoneAStartIndex = Object.keys(this.bufferHeaderOffset).length
+ this.zoneBStartIndex = this.buffer.byteLength / 4 - zoneBBlockSizeInt32
- static getPrimitiveType(type, typedArrayValueIndex) {
- return (type.getTransform(Atomics.load(type.typedArray, typedArrayValueIndex)));
+ if(isMainThread) {
+ this.typedArrays.int32[bufferHeaderOffset.zoneASearch] = zoneAStartIndex
+ this.typedArrays.int32[bufferHeaderOffset.zoneBSearch] = zoneBStartIndex
}
- static setReferenceType(v, type, typedArrayValueIndex) {
+ this.rootObject = new root()
+ }
- if(v === null) {
+ static onWorkerMessage = ({ data }) => {
+ const [name, message] = data
- Atomics.store(type.typedArray, typedArrayValueIndex, 0);
+ console.log(`[${threadName}] BaseClass.onWorkerMessage()`, name, message)
- } else {
+ const { worker, canvas, clsName } = this.workers.get(name)
- Atomics.store(type.typedArray, typedArrayValueIndex, v._b.index);
-
- }
-
- }
+ const params = canvas === null ? [['init', clsName, this.buffer]] : [['init', clsName, this.buffer, canvas], [canvas]]
- static setPrimitiveType(v, type, typedArrayValueIndex) {
+ worker.postMessage(...params)
+ }
- Atomics.store(type.typedArray, typedArrayValueIndex, type.setTransform(v));
+ static init({ root, main, threads }) {
+ // TODO: Add protective errors for invalid params
- }
+ // Create ids for each type and assign to static prop ...
+
+ // this._t = types[this.constructor.name]
- static setRaw(v, type, typedArrayValueIndex) {
+ for (const [k, v] of Object.entries(types)) {
+ console.log(`${key}: ${value}`);
- if(type.referenceType){
- this.setReferenceType(v, type, typedArrayValueIndex);
-
- } else {
- this.setPrimitiveType(v, type, typedArrayValueIndex);
-
- }
}
- static defineObjectProp(o, prop) {
-
- const props = o._b.meta.propsComputed;
-
- const type = props[prop].type;
-
- const typedArrayValueIndex = (o._b.index + props[prop].offset) / type.bytes;
-
- if(type.referenceType) {
-
- Object.defineProperty(o, prop, {
- enumerable: true,
- get() {
- return MemorySystem.getReferenceType(type, typedArrayValueIndex);
- },
- set(v) {
- MemorySystem.setReferenceType(v, type, typedArrayValueIndex);
- }
- });
-
- } else {
-
- Object.defineProperty(o, prop, {
- enumerable: true,
- get() {
- return MemorySystem.getPrimitiveType(type, typedArrayValueIndex);
- },
- set(v) {
- MemorySystem.setPrimitiveType(v, type, typedArrayValueIndex);
- }
- });
-
- }
- }
+ Object.defineProperty(this.prototype, propName, {
+ get: Buffifier.propGetter(name, propName),
+ set: Buffifier.propSetter(v, name, propName),
+ enumerable: true
+ })
+
/*
- static async lockObject() {
-
- return new Promise((resolve) => {
- // 'this' is the object, not MemorySystem
- if (Atomics.compareExchange(MemorySystem.int32Array, this._b.index / 4, 1, 2) !== 1) {
- // Promise apparently is never rejected according to MDN docs.
- Atomics.waitAsync(MemorySystem.int32Array, this._b.index / 4, 2, 2000).value.then((o) => {
- resolve(o);
- });
- } else {
- resolve("not locked");
- }
- });
-
- }
-
- static unlockObject() {
-
- // 'this' is the object, not MemorySystem
-
- if (Atomics.compareExchange(MemorySystem.int32Array, this._b.index / 4, 2, 1) !== 2) {
- throw new Error("Trying to unlock an already unlocked object");
- }
-
- Atomics.notify(MemorySystem.int32Array, this._b.index / 4);
-
- }
+ float64: {
+ //id: 8,
+ cls: ,
+ props: ,
+ bytes: 8,
+ typedArray: Buffifier.uint64Array,
+ get: TypeConverter.uint64ToFloat64,
+ set: TypeConverter.float64ToUint64
+ },
*/
-}
+ if (isMainThread) {
+ this.initMemory(root)
+ let id = 1
-export class Buffifier {
+ for (const t of threads) {
+ const [cls, c] = t.length ? t : [t]
- static rootObject = null;
+ let count = typeof c === 'number' ? c : 1
+ let canvas = typeof c === 'string' ? document.getElementById(c).transferControlToOffscreen() : null
- static isMainThread = null;
-
- static start() {
- this.tick = this.tock;
- this.tick();
- }
+ for (let n = 0; n < count; n++) {
+ const name = cls.name + '_worker' + id++
- static tick = () => undefined;
+ const worker = new Worker('index.js?' + name, { name, type: 'module' })
- static signalWorkers() {
+ worker.addEventListener('message', this.onWorkerMessage)
- const signalIndex = MemorySystem.metaDataIndices.workersSignal;
- const n1 = Atomics.compareExchange(MemorySystem.int32Array, signalIndex, 0, 1);
-
- if(n1 !== 0) {
- throw new Error("signalWorkers: Expected 0 on compareExchange. Got " + n1 + ".");
+ this.workers.set(name, {
+ clsName: cls.name,
+ worker,
+ canvas
+ })
}
-
- const agentsAwoken = Atomics.notify(MemorySystem.int32Array, signalIndex);
-
- const n2 = Atomics.compareExchange(MemorySystem.int32Array, signalIndex, 1, 0);
-
- if(n2 !== 1){
- throw new Error("signalWorkers: Expected 1 on compareExchange. Got " + n2 + ".");
+ }
+ } else {
+ addEventListener('message', ({ data }) => {
+ const [message, clsName, sab, canvas] = data
+ console.log(`[${threadName}] worker received`, message, sab, canvas)
+
+ this.initMemory(root, sab)
+
+ let cls = null
+ for (const t of threads) {
+ const checkCls = t.length ? t[0] : t
+ if (clsName === checkCls.name) {
+ cls = checkCls
+ break
+ }
}
- return agentsAwoken;
- }
-
- static tock() {
-
- requestAnimationFrame(async () => {
-
- const workersSignaled = Buffifier.signalWorkers();
-
- await ThreadSystem.instance.work();
-
- if(workersSignaled < WorkerSystem.workers.length) {
- console.log("ALL WORKERS NOT SIGNALED, WORKERS SIGNALED: " + workersSignaled);
- }
+ const threadInstance = new cls()
- //setTimeout(() => {
- this.tick();
- //}, 500);
+ threadInstance.init(this.rootObject, canvas)
+ })
- });
+ postMessage([threadName, 'loaded'])
}
+ }
- static async initFromWorker(sharedArrayBuffer, threadInstanceIndex, threadId, threadInitConfig) {
+ static valueTypePropGetter() {}
- return new Promise((resolve) => {
+ static valueTypePropSetter() {}
- this.isMainThread = false;
-
- // use the passed in SharedArrayBuffer
- // because we are in a worker thread
- MemorySystem.init(sharedArrayBuffer);
-
- this.rootObject = MemorySystem.getInstance(MemorySystem.rootObjectIndex);
+ static propGetter(name, propName) {
+ /*
+ name,
+ offset,
+ bytes,
+ primitive
- ThreadSystem.initFromWorker(threadId, threadInstanceIndex);
+ */
+ const prop = classes[name].props[propName]
- ThreadSystem.instance.init(this.rootObject, threadInitConfig).then(() => {
- resolve();
- });
- });
-
+ if(prop.primitive) {
+ Atomics.load(this.typedArrays[prop.name], prop.offset)
}
- static async init(rootClass, canvas, options) {
- return new Promise(async (resolve) => {
-
-
- this.isMainThread = true;
-
- const workingOptions = Object.assign({
- allocate: 500000000, // allocates 500 MB for use (has to be evenly divisible by 8)
- rootObjectWorkingSpace: 0 // used if rootClass is variable in size (e.g., BufferedArray)
- }, options);
-
- // create new SharedArrayBuffer because this is on the main thread
- MemorySystem.init(new SharedArrayBuffer(workingOptions.allocate));
+ }
- // create the root object
- this.rootObject = MemorySystem.createInstance(rootClass, null, {
- workingSpace: workingOptions.rootObjectWorkingSpace
- });
-
- await ThreadSystem.init(ThreadSystem.threadIds.main);
-
- await WorkerSystem.init(canvas.transferControlToOffscreen());
-
- resolve(this.rootObject);
-
- });
+ static propSetter(v, name, propName) {
+ return () => {
+
}
+ }
- // nice to have here to not expose MemorySystem
- static createInstance(...args) {
- return MemorySystem.createInstance(...args);
- }
-
-}
-
-export class WorkerSystem {
-
- static messages = {
- LOADED: "loaded",
- INIT: "init",
- READY: "ready"
- };
-
- static workers = [];
-
- static async init(offscreenCanvas) {
- return new Promise(async (resolve) => {
-
- let canvasWorker = null;
-
- for(const instance of ThreadSystem.threadInstances) {
-
- const threadId = instance.threadId;
-
- // if not main thread instance
- if(threadId > 0) {
+ static createInstance = (cls) => {
+ // search blocks starting from allocation search start
+ // once found:
+ // - mark as allocated so no other thread can take it
- const isCanvasThread = (threadId === 1);
+ // TODO: Might be able to speed this up if we set zoneSearch at the end of this. Look into this.
- const workerItem = {
- loaded: false,
- ready: false,
- instance: new Worker("worker.js", { type: "module" }),
- threadId: threadId,
- threadInstance: instance,
- threadInstanceConfig: isCanvasThread ? {
- canvas: offscreenCanvas,
- devicePixelRatio: globalThis.devicePixelRatio
- } : {},
- transferObjects: isCanvasThread ? [offscreenCanvas] : undefined
- };
+ let zone = cls.name === 'List' ? zones.b : zones.a
- console.log("worker launched", workerItem);
+ let index = Atomics.load(this.typedArrays.int32, zone === 0 ? bufferHeaderOffset.zoneASearch : bufferHeaderOffset.zoneBSearch)
- if(isCanvasThread) {
- canvasWorker = workerItem
- } else {
- this.workers.push(workerItem);
- }
- }
- }
-
- console.log("all workers launched", this.workers);
-
- this.workers.push(canvasWorker);
-
- await this.rigWorkers();
-
- resolve();
-
- });
+ const chg = zone === 0 ? this.zoneABlockSizeInt32 : -this.zoneBBlockSizeInt32
+ while(Atomics.compareExchange(this.typedArrays.int32, index, 0, 1) !== 0) {
+ index += chg
}
- static async rigWorkers() {
- return new Promise(async (resolve) => {
- let currentWorkerInitIndex = 0;
-
- const sendNextInit = () => {
-
- if(currentWorkerInitIndex < this.workers.length) {
-
- const w = this.workers[currentWorkerInitIndex];
-
- console.log("sending next init to", w);
-
- w.instance.postMessage([
- this.messages.INIT,
- w.threadInstance._b.index,
- w.threadId,
- w.threadInstanceConfig,
- MemorySystem.buffer,
- w.transferObjects
- ], w.transferObjects);
-
- currentWorkerInitIndex++;
-
- } else {
- resolve();
- }
-
- };
-
- for(const worker of this.workers) {
-
- worker.instance.addEventListener("message", async ({ data }) => {
-
- console.log("worker message received", data, worker);
-
- if(data[0] === this.messages.LOADED) {
-
- worker.loaded = true;
-
- if(this.workers.every(w => w.loaded)) {
-
- sendNextInit();
-
- }
-
- } else if (data[0] === this.messages.READY) {
-
- worker.ready = true;
-
- sendNextInit();
-
- }
-
- });
- }
- });
- }
+ // index is now at the allocated block
- static onWorkersReady () {
- console.log("workers ready!!!");
- }
-
- static onWorkerReceiveMessage = async ({ data }) => {
-
- if(data[0] === this.messages.INIT) {
-
- const threadInstanceIndex = data[1];
- const threadId = data[2];
- const threadInitConfig = data[3];
- const sharedArrayBuffer = data[4];
+
- await Buffifier.initFromWorker(sharedArrayBuffer, threadInstanceIndex, threadId, threadInitConfig);
+ // int32: [0 ,1 ,2 ,3 ]
+ // int16: [0,1,2,3,4,5,6,7]
- postMessage([this.messages.READY]);
+ const genIndex = (index + 1) * 2
+ const typeIndex = genIndex + 1
- this.tick();
+ // increment generation
+ Atomics.add(this.typedArrays.uint16, genIndex, 1)
- }
-
- }
+ // store type
+ Atomics.store(this.typedArrays.uint16, typeIndex, cls._t)
- static async tick() {
- Atomics.wait(MemorySystem.int32Array, MemorySystem.metaDataIndices.workersSignal, 0);
- await ThreadSystem.instance.work();
+ }
- this.tick();
- }
+ static getInstance() {}
+ static deleteInstance(instance) {
+ // increment generation number (will make all pointers null)
+ // set as deallocated
+ // set allocation search start to this block only if this block is less than existing allocation search start.
+ }
}
-class ThreadSystem {
-
- static instance = null;
-
- static totalThreads =
- config.threads.main.length +
- config.threads.canvas.length +
- config.threads.others.length
- ;
-
- static currentThreadId = null;
+class TypeConverter {
+ static buffer = new ArrayBuffer(8)
- static threadIds = {
- main: 0,
- canvas: 1
- };
+ static float32Arr = new Float32Array(this.buffer)
+ static float64Arr = new Float64Array(this.buffer)
- static threadInstances = null;
+ static uint32Array = new Uint32Array(this.buffer)
+ static uint64Array = new BigUint64Array(this.buffer)
- // TODO: Add methods to load/store thread instance meta data
+ static float32ToUint32 = (n) => {
+ this.float32Arr[0] = n
+ return this.uint32Array[0]
+ }
- static initFromWorker(threadId, threadInstanceIndex) {
+ static uint32ToFloat32 = (n) => {
+ this.uint32Array[0] = n
+ return this.float32Arr[0]
+ }
- this.currentThreadId = threadId;
+ static float64ToUint64 = (n) => {
+ this.float64Arr[0] = n
+ return this.uint64Array[0]
+ }
- this.instance = MemorySystem.getInstance(threadInstanceIndex);
+ static uint64ToFloat64 = (n) => {
+ this.uint64Array[0] = n
+ return this.float64Arr[0]
+ }
+ static boolToInt(b) {
+ if (b) {
+ return 1
+ } else {
+ return 0
}
+ }
- static async init(threadId) {
- return new Promise(async (resolve) => {
-
- this.currentThreadId = threadId;
-
- const threadClass = this.getThreadClassById(threadId);
-
- this.instance = MemorySystem.createInstance(threadClass, { threadId });
-
- if(this.threadIds.main === threadId) {
+ static intToBool(n) {
+ return n !== 0
+ }
+}
- // since we are on the main thread, we create all other thread
- // instances here so workers can load the one they are
- // assigned via postMessage -> MemorySystem.getInstance
+const classes = {
+ /*
+ 'App': {
+ cls: App,
+ props: {
+ prop1: {},
+ prop2: {}
+ }
+ }
- this.threadInstances = new Array(this.totalThreads);
+ */
+}
- // add already created main instance
- this.threadInstances[this.threadIds.main] = this.instance;
- // create canvas thread instance
- this.threadInstances[this.threadIds.canvas] =
- MemorySystem.createInstance(this.getThreadClassById(this.threadIds.canvas), {
- threadId: this.threadIds.canvas
- });
-
- let n = 2;
-
- // create the others
- for(const cls of config.threads.others) {
- this.threadInstances[n] = MemorySystem.createInstance(cls, {
- threadId: n
- });
- n++;
- }
+const types = {
+ boolean: {
+ //id: 0,
+
+ bytes: 1,
+ typedArray: Buffifier.typedArrays.uint8
+ get: TypeConverter.intToBool,
+ set: TypeConverter.boolToInt
+ },
+ int8: {
+ //id: 1,
+
+ bytes: 1,
+ typedArray: Buffifier.typedArrays.int8
+ },
+ uint8: {
+ //id: 2,
+
+ bytes: 1,
+ typedArray: Buffifier.typedArrays.uint8
+ },
+ int16: {
+ //id: 3,
+
+ bytes: 2,
+ typedArray: Buffifier.typedArrays.int16
+ },
+ uint16: {
+ //id: 4,
+
+ bytes: 2,
+ typedArray: Buffifier.typedArrays.uint16
+ },
+ int32: {
+ //id: 5,
+
+ bytes: 4,
+ typedArray: Buffifier.typedArrays.int32
+ },
+ uint32: {
+ //id: 6,
+
+ bytes: 4,
+ typedArray: Buffifier.typedArrays.uint32
+ },
+ float32: {
+ //id: 7,
+
+ bytes: 4,
+ typedArray: Buffifier.typedArrays.uint32,
+ get: TypeConverter.uint32ToFloat32,
+ set: TypeConverter.float32ToUint32
+ },
+ float64: {
+ //id: 8,
+
+ bytes: 8,
+ typedArray: Buffifier.typedArrays.uint64,
+ get: TypeConverter.uint64ToFloat64,
+ set: TypeConverter.float64ToUint64
+ },
+ int64: {
+ //id: 9,
+
+ bytes: 8,
+ typedArray: Buffifier.typedArrays.int64
+ },
+ uint64: {
+ //id: 10,
+
+ bytes: 8,
+ typedArray: Buffifier.typedArrays.uint64
+ } /*,
+ object: {
+ bytes: 4,
+ typedArray: Buffifier.uint32Array
+ }*/
+}
- await this.instance.init(Buffifier.rootObject);
+export class BaseClass {
+ static buffify = Buffifier.buffify
+
+ _value = null
+ _props = null
+
+ /*
+ static buffify(o) {
+ console.log(`[${threadName}] BaseClass.buffify()`, this.name, o)
+ }
+ */
+
+ constructor(v) {
+ console.log(`[${threadName}] BaseClass.constructor()`, v)
+
+ if (v === undefined) {
+ // unallocated instance
+ } else if (typeof v === 'object') {
+ // unallocated instance with defaults
+ } else {
+ // assume it's a number and it's an existing instance
+ // v is the block index
+ this._value = v
+ }
+
+ // if v === undefined
+ // it's a new instance so search for free block to set value (blockIndex?)
+ // else
+ // if value type, store v as value.
+ // else v is the block index, store as value (blockIndex)
+ }
+}
- resolve();
- }
+export class List extends BaseClass {
+ static {
+ this.buffify({
+ allocate: 262140, // allows about 65535 items
+ props: [
+ ['of', 'uint16'],
+ ['count', 'uint16', 0]
+ ]
+ })
+ }
- });
+ listType = null
- }
+ constructor(of) {
+ /*
+ Constructor will be called by
+ user via new List(GridCell)
+ - 'of' needs to be set to type id by calling super with { of: 5 }
+
+ or...
+ buffifier via new List(blockIndex)
+ - 'of' and 'count' will already be good.
+ */
- static getThreadClassById(threadId) {
+ super({
+ of: types[of.name].id
+ })
- if(threadId === 0) {
- return config.threads.main[0];
- } else if(threadId === 1) {
- return config.threads.canvas[0];
- } else if(threadId > 1) {
- return config.threads.others[threadId - 2];
- }
+ /*
+ if(typeof o === 'string') {
+ } else if (o instanceof BaseClass) {
+ const name = o.name
+ } else {
+ throw new Error (`List constructor argument must be a string ('int16', 'uint32', ...) or a class extending BaseClass`)
}
-
+ */
+ }
}
-
-
diff --git a/Entity.js b/Entity.js
deleted file mode 100644
index baf8d5a..0000000
--- a/Entity.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { DataTypes } from "./Buffifier.js";
-
-export class Entity {
-
- width = 2;
- depth = 2;
- height = 10;
-
- static _meta = {
-
- get props() {
-
- return {
-
- x: DataTypes.Float64,
- y: DataTypes.Float64,
- z: DataTypes.Float64,
-
- mouseOver: DataTypes.boolean,
-
- speed: DataTypes.Float32,
-
- screenX: DataTypes.Int32,
- screenY: DataTypes.Int32,
- cameraDistance: DataTypes.Float32
- }
-
- }
-
- };
-
-}
diff --git a/README.md b/README.md
deleted file mode 100644
index f7c5f4c..0000000
--- a/README.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# Buffifier
-Currently a work in progress.
-
-Buffifier is a framework that tackles the problem of having to communicate large amounts of data to multiple web workers with low latency. This is accomplished with SharedArrayBuffer (SAB), Atomics, and intercepting object properties via setters/getters. It tries to be as minimally invasive as possible by not requiring you to change much of your app. This repository is a minimal example that will demonstrate its capabilities, and will show you how to use the framework. The "meat and potatoes" is in Buffifier.js, and ideally, that is the only file you will need to include in your app.
-
----
-
-**How to see it in action:**
-
-Download/clone this repository.
-
-Run this command in the same folder to start a simple web server: ``py -m secure_server``
-
-Alternatively, setup any simple web server that serves the files with these response headers:
-
-```
-Cross-Origin-Opener-Policy: same-origin
-Cross-Origin-Embedder-Policy: require-corp
-```
-
-Headers are required for SharedArrayBuffer and unthrottled performance.now() timers.
-
-Navigate to the http://localhost:8000, and you should see something like this:
-
-
-
-(_it's a work in progress, so it only shows a still Three.js scene for now. But the inner workers should be automatically reading/writing to the SAB_)
-
----
-
-**How to set up a class so instances are shared across web workers:**
-
-(_instructions are incomplete_)
-
-### RootClass.js
-```javascript
-import { DataTypes } from "./Buffifier.js";
-
-export class RootClass {
-
- // Not shared accross web workers
- someProperty = 10;
-
- // Special static property that defines the class for Buffifier
- static _meta = {
- get props() {
- return {
- // Define shared properties here with any type in DataTypes
- someSharedProperty: DataTypes.Int32
- }
- }
- };
-}
-```
-
-### Buffifier.js
-You'll need to edit Buffifier.js a bit to work with your application.
-```javascript
-import { RootClass } from "./RootClass.js";
-import { MainThread } from "./MainThread.js";
-import { CanvasThread } from "./CanvasThread.js";
-import { OtherThread } from "./OtherThread.js";
-
-const config = {
- classes: [
- RootClass
- ],
- threads: {
- main: [
- MainThread
- ],
- canvas: [
- CanvasThread
- ],
- others: [
- OtherThread
- ]
- }
-};
-
-export const DataTypes = {
- // Add your classes to this list so they can be identified properly
- RootClass: 12,
- MainThread: 13,
- CanvasThread: 14,
- OtherThread: 15
-}
-```
-
-### main.js
-```javascript
-import { RootClass } from "./RootClass.js";
-
-// Initializes Buffifier:
-// - Starts all web workers
-// - Creates an instance of RootClass that is backed by SharedArrayBuffer.
-// - Gives all web workers access to the same RootClass instance.
-// - Starts an animation frame loop that will execute some method on all threads: MainThread.js, CanvasThread.js, OtherThread.js.
-const rootClassInstance = Buffifier.init(RootClass);
-
-// Web workers will immediatly have access to someSharedProperty's new value after this statement executes here.
-rootClassInstance.someSharedProperty = 10;
-
-// Web workers will not see changes of someProperty's value on their side because it's not a shared property.
-rootClassInstance.someProperty = 10;
-
-```
diff --git a/RateCounter.js b/RateCounter.js
deleted file mode 100644
index 233c8c3..0000000
--- a/RateCounter.js
+++ /dev/null
@@ -1,38 +0,0 @@
-
-export class RateCounter {
-
- value = 0;
-
- #samples = null;
- #last = null;
-
- #sum = 0;
- #count = 0;
-
- log = () => {
-
- const ct = document.timeline.currentTime;
-
- this.#sum += (ct - this.#last);
-
- if(this.#count === this.#samples) {
- this.value = (this.#samples * 1000) / this.#sum;
- this.#sum = 0;
- this.#count = 1;
- } else {
- this.#count++;
- }
-
- this.#last = ct;
-
- };
-
-
- constructor(samples = 30) {
- this.#samples = samples;
- this.#last = document.timeline.currentTime;
- }
-
-}
-
-
diff --git a/World.js b/World.js
deleted file mode 100644
index 6ce928f..0000000
--- a/World.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { DataTypes } from "./Buffifier.js";
-
-export class World {
-
- static _meta = {
- get props() {
- return {
- heightData: DataTypes.BufferedArray,
- items: DataTypes.BufferedArray
- }
- }
- };
-
-
- static cellSize = 10;
-
- static segments = 60;
-
- static get cellsZ() {
- return this.segments;
- }
-
- static get cellsX() {
- return this.segments;
- }
-
- static get size() {
- return this.cellSize * this.segments;
- }
-
- static get width() {
- return this.size;
- }
-
- static get depth() {
- return this.size;
- }
-
-
-
-
-
-
-}
diff --git a/favicon.ico b/favicon.ico
deleted file mode 100644
index 242262e..0000000
Binary files a/favicon.ico and /dev/null differ
diff --git a/index.html b/index.html
index bc7dc70..da9048d 100644
--- a/index.html
+++ b/index.html
@@ -1,46 +1,45 @@
-
+
-