mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-21 17:42:34 +02:00
Better shaders. Scrolling pool. Better transitions
This commit is contained in:
parent
54a867ffaa
commit
72fdcf2601
@ -21,15 +21,15 @@
|
|||||||
export let running = false
|
export let running = false
|
||||||
|
|
||||||
// Shader attributes
|
// Shader attributes
|
||||||
|
// each attribute contains [x: startValue, y: endValue, z: startTime, w: rate]
|
||||||
|
// shader interpolates between start and end values at the given rate, from the given time
|
||||||
const attribs = {
|
const attribs = {
|
||||||
startTime: { type: 'FLOAT', count: 1, pointer: null },
|
posX: { type: 'FLOAT', count: 4, pointer: null },
|
||||||
zIndex: { type: 'FLOAT', count: 1, pointer: null },
|
posY: { type: 'FLOAT', count: 4, pointer: null },
|
||||||
speed: { type: 'FLOAT', count: 1, pointer: null },
|
sizes: { type: 'FLOAT', count: 4, pointer: null },
|
||||||
positions: { type: 'FLOAT', count: 4, pointer: null },
|
palettes: { type: 'FLOAT', count: 4, pointer: null },
|
||||||
sizes: { type: 'FLOAT', count: 2, pointer: null },
|
colors: { type: 'FLOAT', count: 4, pointer: null },
|
||||||
palettes: { type: 'FLOAT', count: 2, pointer: null },
|
alphas: { type: 'FLOAT', count: 4, pointer: null }
|
||||||
colors: { type: 'FLOAT', count: 2, pointer: null },
|
|
||||||
alphas: { type: 'FLOAT', count: 2, pointer: null }
|
|
||||||
}
|
}
|
||||||
// Auto-calculate the number of bytes per vertex based on specified attributes
|
// Auto-calculate the number of bytes per vertex based on specified attributes
|
||||||
const stride = Object.values(attribs).reduce((total, attrib) => {
|
const stride = Object.values(attribs).reduce((total, attrib) => {
|
||||||
@ -58,9 +58,10 @@
|
|||||||
|
|
||||||
function getTxPointArray () {
|
function getTxPointArray () {
|
||||||
if (controller) {
|
if (controller) {
|
||||||
return new Float32Array(
|
return controller.getVertexData()
|
||||||
controller.getScenes().flatMap(scene => scene.getVertexData())
|
// return new Float32Array(
|
||||||
)
|
// controller.getScenes().flatMap(scene => scene.getVertexData())
|
||||||
|
// )
|
||||||
} else return []
|
} else return []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +137,9 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
/* DRAW */
|
/* DRAW */
|
||||||
gl.drawArrays(gl.POINTS, 0, pointArray.length / 3)
|
if (pointArray.length) {
|
||||||
|
gl.drawArrays(gl.POINTS, 0, pointArray.length / 3)
|
||||||
|
}
|
||||||
|
|
||||||
/* LOOP */
|
/* LOOP */
|
||||||
window.requestAnimationFrame(currentTime => {
|
window.requestAnimationFrame(currentTime => {
|
||||||
@ -214,6 +217,8 @@
|
|||||||
colorTexture = loadColorTexture(gl, '#f7941d', 'rgb(0%,100%,80%)', 500);
|
colorTexture = loadColorTexture(gl, '#f7941d', 'rgb(0%,100%,80%)', 500);
|
||||||
|
|
||||||
running = true
|
running = true
|
||||||
|
|
||||||
|
console.log(this)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -222,9 +227,9 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: -5px;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
pointer-events: none;
|
/* pointer-events: none; */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,13 +3,12 @@
|
|||||||
import TxController from '../controllers/TxController.js'
|
import TxController from '../controllers/TxController.js'
|
||||||
import TxRender from './TxRender.svelte'
|
import TxRender from './TxRender.svelte'
|
||||||
import getTxStream from '../controllers/TxStream.js'
|
import getTxStream from '../controllers/TxStream.js'
|
||||||
import { darkMode, serverConnected, serverDelay, txQueueLength } from '../stores.js'
|
import { darkMode, serverConnected, serverDelay, txQueueLength, txCount } from '../stores.js'
|
||||||
import BitcoinBlock from '../models/BitcoinBlock.js'
|
import BitcoinBlock from '../models/BitcoinBlock.js'
|
||||||
|
|
||||||
let width = window.innerWidth
|
let width = window.innerWidth
|
||||||
let height = window.innerHeight
|
let height = window.innerHeight
|
||||||
let txController
|
let txController
|
||||||
let txCount = 0
|
|
||||||
let blockCount = 0
|
let blockCount = 0
|
||||||
let running = false
|
let running = false
|
||||||
let txStream = getTxStream()
|
let txStream = getTxStream()
|
||||||
@ -53,6 +52,10 @@
|
|||||||
// }))
|
// }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fakeTx () {
|
||||||
|
txController.simulateDumpTx(1)
|
||||||
|
}
|
||||||
|
|
||||||
function fakeTxs () {
|
function fakeTxs () {
|
||||||
txController.simulateDumpTx(200)
|
txController.simulateDumpTx(200)
|
||||||
}
|
}
|
||||||
@ -107,6 +110,10 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
.status-light {
|
.status-light {
|
||||||
display: block;
|
display: block;
|
||||||
@ -124,6 +131,20 @@
|
|||||||
background: greenyellow;
|
background: greenyellow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-counter {
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
&.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
&.amber {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
&.green {
|
||||||
|
color: greenyellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -134,12 +155,14 @@
|
|||||||
<TxRender controller={txController} />
|
<TxRender controller={txController} />
|
||||||
</div>
|
</div>
|
||||||
<div class="sim-controls">
|
<div class="sim-controls">
|
||||||
|
<button on:click={fakeTx}>TXN</button>
|
||||||
<button on:click={fakeTxs}>TXNS</button>
|
<button on:click={fakeTxs}>TXNS</button>
|
||||||
<button on:click={fakeBlock}>BLOCK</button>
|
<button on:click={fakeBlock}>BLOCK</button>
|
||||||
<button on:click={toggleDark}>{$darkMode ? 'LIGHT' : 'DARK' }</button>
|
<button on:click={toggleDark}>{$darkMode ? 'LIGHT' : 'DARK' }</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<span class="queue-length">{ $txQueueLength }</span>
|
|
||||||
<div class="status-light {connectionColor}" title={connectionTitle}></div>
|
<div class="status-light {connectionColor}" title={connectionTitle}></div>
|
||||||
|
<span class="stat-counter {connectionColor}">{ $txQueueLength }</span>
|
||||||
|
<span class="stat-counter {connectionColor}">{ $txCount }</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,15 @@ import TxPoolScene from '../models/TxPoolScene.js'
|
|||||||
import TxBlockScene from '../models/TxBlockScene.js'
|
import TxBlockScene from '../models/TxBlockScene.js'
|
||||||
import BitcoinTx from '../models/BitcoinTx.js'
|
import BitcoinTx from '../models/BitcoinTx.js'
|
||||||
import BitcoinBlock from '../models/BitcoinBlock.js'
|
import BitcoinBlock from '../models/BitcoinBlock.js'
|
||||||
|
import { FastVertexArray } from '../utils/memory.js'
|
||||||
import { txQueueLength } from '../stores.js'
|
import { txQueueLength } from '../stores.js'
|
||||||
|
|
||||||
export default class TxController {
|
export default class TxController {
|
||||||
constructor ({ width, height }) {
|
constructor ({ width, height }) {
|
||||||
|
this.vertexArray = new FastVertexArray(2048, 24)
|
||||||
this.txs = {}
|
this.txs = {}
|
||||||
this.expiredTxs = {}
|
this.expiredTxs = {}
|
||||||
this.pool = new TxPoolScene({ width, height, layer: 0.0 })
|
this.pool = new TxPoolScene({ width, height, layer: 0.0, controller: this })
|
||||||
this.blocks = {}
|
this.blocks = {}
|
||||||
this.clearBlockTimeout = null
|
this.clearBlockTimeout = null
|
||||||
|
|
||||||
@ -19,6 +21,10 @@ export default class TxController {
|
|||||||
this.scheduleQueue(1000)
|
this.scheduleQueue(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVertexData () {
|
||||||
|
return this.vertexArray.getVertexData()
|
||||||
|
}
|
||||||
|
|
||||||
getScenes () {
|
getScenes () {
|
||||||
return [this.pool, ...Object.values(this.blocks)]
|
return [this.pool, ...Object.values(this.blocks)]
|
||||||
}
|
}
|
||||||
@ -30,7 +36,8 @@ export default class TxController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addTx (tx) {
|
addTx (txData) {
|
||||||
|
const tx = new BitcoinTx(txData, this.vertexArray)
|
||||||
if (!this.txs[tx.id] && !this.expiredTxs[tx.id]) {
|
if (!this.txs[tx.id] && !this.expiredTxs[tx.id]) {
|
||||||
this.pendingTxs.push([tx, Date.now()])
|
this.pendingTxs.push([tx, Date.now()])
|
||||||
txQueueLength.increment()
|
txQueueLength.increment()
|
||||||
@ -77,7 +84,8 @@ export default class TxController {
|
|||||||
}, delay)
|
}, delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
addBlock (block) {
|
addBlock (blockData) {
|
||||||
|
const block = new BitcoinBlock(blockData)
|
||||||
if (this.clearBlockTimeout) clearTimeout(this.clearBlockTimeout)
|
if (this.clearBlockTimeout) clearTimeout(this.clearBlockTimeout)
|
||||||
|
|
||||||
this.expiredTxs = {}
|
this.expiredTxs = {}
|
||||||
@ -86,7 +94,7 @@ export default class TxController {
|
|||||||
if (!this.blocks[blockId].expired) this.clearBlock(blockId)
|
if (!this.blocks[blockId].expired) this.clearBlock(blockId)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.blocks[block.id] = new TxBlockScene({ width: 500, height: 500, layer: 1.0 })
|
this.blocks[block.id] = new TxBlockScene({ width: 500, height: 500, layer: 1.0, blockId: block.id, controller: this })
|
||||||
let knownCount = 0
|
let knownCount = 0
|
||||||
let unknownCount = 0
|
let unknownCount = 0
|
||||||
for (let i = 0; i < block.txns.length; i++) {
|
for (let i = 0; i < block.txns.length; i++) {
|
||||||
@ -99,14 +107,14 @@ export default class TxController {
|
|||||||
const tx = new BitcoinTx({
|
const tx = new BitcoinTx({
|
||||||
...block.txns[i],
|
...block.txns[i],
|
||||||
block: block.id
|
block: block.id
|
||||||
})
|
}, this.vertexArray)
|
||||||
this.txs[tx.id] = tx
|
this.txs[tx.id] = tx
|
||||||
this.blocks[block.id].insert(this.txs[tx.id], false)
|
this.blocks[block.id].insert(this.txs[tx.id], false)
|
||||||
}
|
}
|
||||||
this.expiredTxs[block.txns[i].id] = true
|
this.expiredTxs[block.txns[i].id] = true
|
||||||
}
|
}
|
||||||
console.log(`New block with ${knownCount} known transactions and ${unknownCount} unknown transactions`)
|
console.log(`New block with ${knownCount} known transactions and ${unknownCount} unknown transactions`)
|
||||||
this.blocks[block.id].layoutAll()
|
this.blocks[block.id].initialLayout()
|
||||||
setTimeout(() => { this.pool.layoutAll() }, 2000)
|
setTimeout(() => { this.pool.layoutAll() }, 2000)
|
||||||
|
|
||||||
this.clearBlockTimeout = setTimeout(() => { this.clearBlock(block.id) }, 10000)
|
this.clearBlockTimeout = setTimeout(() => { this.clearBlock(block.id) }, 10000)
|
||||||
@ -129,7 +137,7 @@ export default class TxController {
|
|||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
const simulatedTxns = []
|
const simulatedTxns = []
|
||||||
Object.values(this.txs).forEach(tx => {
|
Object.values(this.pool.txs).forEach(tx => {
|
||||||
if (Math.random() < 0.5) {
|
if (Math.random() < 0.5) {
|
||||||
simulatedTxns.push({
|
simulatedTxns.push({
|
||||||
version: tx.version,
|
version: tx.version,
|
||||||
@ -150,7 +158,7 @@ export default class TxController {
|
|||||||
txn_count: 20,
|
txn_count: 20,
|
||||||
txns: simulatedTxns
|
txns: simulatedTxns
|
||||||
}))
|
}))
|
||||||
}, 2500)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
simulateDumpTx (n) {
|
simulateDumpTx (n) {
|
||||||
@ -160,22 +168,25 @@ export default class TxController {
|
|||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
id: `simulated_${i}_${Math.random()}`,
|
id: `simulated_${i}_${Math.random()}`,
|
||||||
value: Math.floor(Math.random() * 100000)
|
value: Math.floor(Math.random() * 100000)
|
||||||
}))
|
}, this.vertexArray))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearBlock (id) {
|
clearBlock (id) {
|
||||||
if (this.blocks[id]) {
|
if (this.blocks[id]) {
|
||||||
this.blocks[id].expire()
|
this.blocks[id].expire()
|
||||||
setTimeout(() => {
|
|
||||||
const txs = this.blocks[id].getTxList()
|
|
||||||
for (let i = 0; i < txs.length; i++) {
|
|
||||||
if (this.blocks[id].remove(txs[i])) {
|
|
||||||
delete this.txs[txs[i]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete this.blocks[id]
|
|
||||||
}, 3000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroyTx (id) {
|
||||||
|
this.getScenes().forEach(scene => {
|
||||||
|
scene.remove(id)
|
||||||
|
})
|
||||||
|
if (this.txs[id]) this.txs[id].destroy()
|
||||||
|
delete this.txs[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyBlock (id) {
|
||||||
|
if (this.blocks) delete this.blocks[id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import BitcoinTx from '../models/BitcoinTx.js'
|
|
||||||
import BitcoinBlock from '../models/BitcoinBlock.js'
|
|
||||||
import { serverConnected, serverDelay } from '../stores.js'
|
import { serverConnected, serverDelay } from '../stores.js'
|
||||||
|
|
||||||
class TxStream {
|
class TxStream {
|
||||||
@ -88,12 +86,10 @@ class TxStream {
|
|||||||
} else {
|
} else {
|
||||||
const msg = JSON.parse(event.data)
|
const msg = JSON.parse(event.data)
|
||||||
if (msg && msg.type === 'txn') {
|
if (msg && msg.type === 'txn') {
|
||||||
const tx = new BitcoinTx(msg.txn)
|
window.dispatchEvent(new CustomEvent('bitcoin_tx', { detail: msg.txn }))
|
||||||
window.dispatchEvent(new CustomEvent('bitcoin_tx', { detail: tx }))
|
|
||||||
} else if (msg && msg.type === 'block') {
|
} else if (msg && msg.type === 'block') {
|
||||||
const block = new BitcoinBlock(msg.block)
|
console.log('Block recieved: ', msg.block)
|
||||||
console.log('Block recieved: ', block)
|
window.dispatchEvent(new CustomEvent('bitcoin_block', { detail: msg.block }))
|
||||||
window.dispatchEvent(new CustomEvent('bitcoin_block', { detail: block }))
|
|
||||||
} else {
|
} else {
|
||||||
console.log('unknown message from websocket: ', msg)
|
console.log('unknown message from websocket: ', msg)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import TxView from './TxView.js'
|
import TxView from './TxView.js'
|
||||||
|
|
||||||
export default class BitcoinTx {
|
export default class BitcoinTx {
|
||||||
constructor ({ version, id, value, inputs, outputs, witnesses, time, block }) {
|
constructor ({ version, id, value, inputs, outputs, witnesses, time, block }, vertexArray) {
|
||||||
this.version = version
|
this.version = version
|
||||||
this.id = id
|
this.id = id
|
||||||
|
this.vertexArray = vertexArray
|
||||||
|
|
||||||
if (inputs && outputs) {
|
if (inputs && outputs) {
|
||||||
this.inputs = inputs
|
this.inputs = inputs
|
||||||
@ -20,6 +21,10 @@ export default class BitcoinTx {
|
|||||||
this.view = new TxView(this)
|
this.view = new TxView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
if (this.view) this.view.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
calcValue () {
|
calcValue () {
|
||||||
if (this.outputs && this.outputs.length) {
|
if (this.outputs && this.outputs.length) {
|
||||||
return this.outputs.reduce((acc, output) => {
|
return this.outputs.reduce((acc, output) => {
|
||||||
@ -37,7 +42,11 @@ export default class BitcoinTx {
|
|||||||
if (this.view) this.view.update(update)
|
if (this.view) this.view.update(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPosition (position) {
|
||||||
|
this.position = position
|
||||||
|
}
|
||||||
|
|
||||||
getPosition () {
|
getPosition () {
|
||||||
if (this.view) return this.view.getPosition()
|
if (this.position) return this.position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import TxPoolScene from './TxPoolScene.js'
|
import TxPoolScene from './TxPoolScene.js'
|
||||||
|
|
||||||
export default class TxBlockScene extends TxPoolScene {
|
export default class TxBlockScene extends TxPoolScene {
|
||||||
constructor ({ width, height, unit = 4, padding = 1, layer }) {
|
constructor ({ width, height, unit = 4, padding = 1, layer, blockId, controller }) {
|
||||||
super({ width, height, unit, padding, layer })
|
super({ width, height, unit, padding, layer, controller })
|
||||||
this.heightLimit = null
|
this.heightLimit = null
|
||||||
this.expired = false
|
this.expired = false
|
||||||
|
this.layedOut = false
|
||||||
|
this.blockId = blockId
|
||||||
}
|
}
|
||||||
|
|
||||||
resize ({ width, height, unit, padding }) {
|
resize ({ width, height, unit, padding }) {
|
||||||
@ -48,7 +50,8 @@ export default class TxBlockScene extends TxPoolScene {
|
|||||||
state: 'ready'
|
state: 'ready'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const flyToBlock = {
|
|
||||||
|
this.updateTx(tx, {
|
||||||
display: {
|
display: {
|
||||||
position,
|
position,
|
||||||
size: 4,
|
size: 4,
|
||||||
@ -60,33 +63,44 @@ export default class TxBlockScene extends TxPoolScene {
|
|||||||
},
|
},
|
||||||
duration: 1500,
|
duration: 1500,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
state: 'flytoblock',
|
state: 'block'
|
||||||
next: {
|
})
|
||||||
display: {},
|
}
|
||||||
state: 'block'
|
|
||||||
}
|
prepareTx (tx, sequence) {
|
||||||
}
|
const position = this.place(tx.id, sequence)
|
||||||
if (tx.view.state === 'pool' || tx.view.state === 'colorfade') {
|
if (!tx.view.initialised) {
|
||||||
this.updateTx(tx, {
|
this.updateTx(tx, {
|
||||||
display: {
|
display: {
|
||||||
layer: this.layer,
|
layer: this.layer,
|
||||||
|
position: {
|
||||||
|
x: window.innerWidth * ((position.x - this.scene.offset.x) / this.width),
|
||||||
|
y: window.innerHeight * (1 + ((position.y - this.scene.offset.y) / this.height))
|
||||||
|
},
|
||||||
|
size: 8,
|
||||||
color: {
|
color: {
|
||||||
palette: 0,
|
palette: 0,
|
||||||
index: 0,
|
index: 0,
|
||||||
alpha: 1
|
alpha: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
duration: 1000,
|
duration: 1500,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
state: 'colorfade',
|
state: 'ready'
|
||||||
next: flyToBlock
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.updateTx(tx, {
|
|
||||||
...flyToBlock,
|
|
||||||
duration: 2500
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this.updateTx(tx, {
|
||||||
|
display: {
|
||||||
|
layer: this.layer,
|
||||||
|
color: {
|
||||||
|
palette: 0,
|
||||||
|
index: 0,
|
||||||
|
alpha: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
duration: 2000,
|
||||||
|
delay: 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
expireTx (tx) {
|
expireTx (tx) {
|
||||||
@ -103,9 +117,30 @@ export default class TxBlockScene extends TxPoolScene {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareAll () {
|
||||||
|
this.resize({})
|
||||||
|
this.scene.count = 0
|
||||||
|
let ids = this.getHiddenTxList()
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
this.txs[ids[i]] = this.hiddenTxs[ids[i]]
|
||||||
|
delete this.hiddenTxs[ids[i]]
|
||||||
|
}
|
||||||
|
ids = this.getActiveTxList()
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
this.prepareTx(this.txs[ids[i]], this.scene.count++)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layoutAll () {
|
layoutAll () {
|
||||||
this.resize({})
|
this.resize({})
|
||||||
super.layoutAll(4)
|
super.layoutAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
initialLayout () {
|
||||||
|
this.prepareAll()
|
||||||
|
setTimeout(() => {
|
||||||
|
this.layoutAll()
|
||||||
|
}, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
expire () {
|
expire () {
|
||||||
@ -114,5 +149,12 @@ export default class TxBlockScene extends TxPoolScene {
|
|||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
this.expireTx(this.txs[ids[i]])
|
this.expireTx(this.txs[ids[i]])
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
const txIds = this.getTxList()
|
||||||
|
for (let i = 0; i < txIds.length; i++) {
|
||||||
|
if (this.txs[txIds[i]]) this.controller.destroyTx(txIds[i])
|
||||||
|
}
|
||||||
|
this.controller.destroyBlock(this.blockId)
|
||||||
|
}, 3000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
export default class TxPoolScene {
|
export default class TxPoolScene {
|
||||||
constructor ({ width, height, unit, padding, layer }) {
|
constructor ({ width, height, unit, padding, layer, controller }) {
|
||||||
this.init({ width, height, unit, padding, layer })
|
this.init({ width, height, unit, padding, layer, controller })
|
||||||
}
|
}
|
||||||
|
|
||||||
init ({ width, height, unit = 8, padding = 1, layer }) {
|
init ({ width, height, unit = 8, padding = 1, layer, controller }) {
|
||||||
|
this.controller = controller
|
||||||
this.layer = layer
|
this.layer = layer
|
||||||
this.resize({ width, height, unit, padding })
|
this.resize({ width, height, unit, padding })
|
||||||
this.txs = {}
|
this.txs = {}
|
||||||
this.hiddenTxs = {}
|
this.hiddenTxs = {}
|
||||||
this.heightLimit = this.height - 50
|
|
||||||
|
this.heightLimit = Math.max(150, height / 4)
|
||||||
|
this.heightBound = this.height - this.heightLimit
|
||||||
|
this.poolTop = this.height
|
||||||
|
this.poolBottom = this.height
|
||||||
|
|
||||||
this.scene = {
|
this.scene = {
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
@ -21,6 +27,8 @@ export default class TxPoolScene {
|
|||||||
}
|
}
|
||||||
this.scrollRateLimitTimer = null
|
this.scrollRateLimitTimer = null
|
||||||
this.initialised = true
|
this.initialised = true
|
||||||
|
|
||||||
|
console.log('pool', this)
|
||||||
}
|
}
|
||||||
|
|
||||||
resize ({ width, height, unit, padding }) {
|
resize ({ width, height, unit, padding }) {
|
||||||
@ -37,14 +45,8 @@ export default class TxPoolScene {
|
|||||||
if (tx) tx.updateView(update)
|
if (tx) tx.updateView(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll (offset) {
|
getPoolHeight () {
|
||||||
if (!this.scrollRateLimitTimer || Date.now() < (this.scrollRateLimitTimer + 10000)) {
|
return this.poolBottom - this.poolTop
|
||||||
console.log(`scrolling pool by ${offset}`)
|
|
||||||
this.scrollRateLimitTimer = Date.now()
|
|
||||||
this.doScroll(offset)
|
|
||||||
} else {
|
|
||||||
console.log('scroll recharging')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
insert (tx, autoLayout=true) {
|
insert (tx, autoLayout=true) {
|
||||||
@ -56,36 +58,81 @@ export default class TxPoolScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearOffscreenTx (tx) {
|
||||||
|
const currentTargetPosition = tx.getPosition()
|
||||||
|
if (currentTargetPosition && (currentTargetPosition.y + this.scene.scroll) > this.height + 150) {
|
||||||
|
this.controller.destroyTx(tx.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearOffscreenTxs () {
|
||||||
|
if (this.poolBottom + this.scene.scroll > (this.height + 150)) {
|
||||||
|
const ids = this.getTxList()
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
this.clearOffscreenTx(this.txs[ids[i]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scrollTx (tx, scrollDistance) {
|
scrollTx (tx, scrollDistance) {
|
||||||
if (tx.view.initialised) {
|
if (tx.view.initialised) {
|
||||||
let currentPosition = tx.getPosition()
|
let currentTargetPosition = tx.getPosition()
|
||||||
this.updateTx(tx, {
|
if (currentTargetPosition) {
|
||||||
display: {
|
this.updateTx(tx, {
|
||||||
position: {
|
display: {
|
||||||
y: currentPosition.y + scrollDistance
|
position: {
|
||||||
}
|
y: currentTargetPosition.y + scrollDistance
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
duration: 500,
|
||||||
|
minDuration: 250,
|
||||||
|
adjust: true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doScroll (offset) {
|
doScroll (offset) {
|
||||||
const ids = this.getActiveTxList()
|
const ids = this.getTxList()
|
||||||
for (let i = 0; i < ids.length; i++) {
|
|
||||||
this.scrollTx(this.txs[ids[i]], offset)
|
|
||||||
}
|
|
||||||
this.scene.scroll -= offset
|
this.scene.scroll -= offset
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
this.scrollTx(this.txs[ids[i]], this.scene.scroll)
|
||||||
|
}
|
||||||
|
this.clearOffscreenTxs()
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll (offset, force) {
|
||||||
|
if (!this.scrollRateLimitTimer || force || Date.now() > (this.scrollRateLimitTimer + 1000)) {
|
||||||
|
this.scrollRateLimitTimer = Date.now()
|
||||||
|
this.doScroll(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txSize (value) {
|
||||||
|
// let scale = Math.log10(value)
|
||||||
|
// let size = (scale*scale) / 5
|
||||||
|
// let rounded = Math.pow(2, Math.ceil(Math.log2(size)))
|
||||||
|
// return Math.max(4, rounded)
|
||||||
|
return this.unitWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutTx (tx, sequence) {
|
layoutTx (tx, sequence) {
|
||||||
const position = this.place(tx.id, sequence)
|
const rawPosition = this.place(tx.id, sequence)
|
||||||
// if (this.heightLimit && position.y < this.heightLimit) this.scroll(position.y - this.heightLimit)
|
const scrolledPosition = {
|
||||||
|
x: rawPosition.x,
|
||||||
|
y: rawPosition.y + this.scene.scroll
|
||||||
|
}
|
||||||
|
tx.setPosition(rawPosition)
|
||||||
|
if (this.heightLimit && scrolledPosition.y < this.heightBound) {
|
||||||
|
this.scroll(scrolledPosition.y - this.heightBound)
|
||||||
|
scrolledPosition.y = rawPosition.y + this.scene.scroll
|
||||||
|
}
|
||||||
if (!tx.view.initialised) {
|
if (!tx.view.initialised) {
|
||||||
this.updateTx(tx, {
|
this.updateTx(tx, {
|
||||||
display: {
|
display: {
|
||||||
layer: this.layer,
|
layer: this.layer,
|
||||||
position: {
|
position: {
|
||||||
x: position.x,
|
x: scrolledPosition.x,
|
||||||
y: 0
|
y: 0
|
||||||
},
|
},
|
||||||
size: this.unitWidth,
|
size: this.unitWidth,
|
||||||
@ -102,8 +149,9 @@ export default class TxPoolScene {
|
|||||||
this.updateTx(tx, {
|
this.updateTx(tx, {
|
||||||
display: {
|
display: {
|
||||||
layer: this.layer,
|
layer: this.layer,
|
||||||
position,
|
position: scrolledPosition,
|
||||||
size: this.unitWidth,
|
// size: this.unitWidth,
|
||||||
|
size: this.txSize(tx.value),
|
||||||
color: {
|
color: {
|
||||||
palette: 0,
|
palette: 0,
|
||||||
index: 0,
|
index: 0,
|
||||||
@ -112,60 +160,35 @@ export default class TxPoolScene {
|
|||||||
},
|
},
|
||||||
duration: 1500,
|
duration: 1500,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
state: 'entering',
|
state: 'pool'
|
||||||
next: {
|
})
|
||||||
display: {
|
this.updateTx(tx, {
|
||||||
color: {
|
display: {
|
||||||
palette: 0,
|
color: {
|
||||||
index: 1
|
palette: 0,
|
||||||
}
|
index: 1
|
||||||
},
|
|
||||||
duration: 30000,
|
|
||||||
delay: 100,
|
|
||||||
state: 'colorfade',
|
|
||||||
next: {
|
|
||||||
display: {},
|
|
||||||
state: 'pool'
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
duration: 30000,
|
||||||
|
delay: 0
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const shuffleUpdate = {
|
this.updateTx(tx, {
|
||||||
display: {
|
display: {
|
||||||
position
|
position: scrolledPosition
|
||||||
},
|
},
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
|
minDuration: 1000,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
state: 'shuffling',
|
adjust: true
|
||||||
next: {
|
})
|
||||||
display: {
|
|
||||||
color: {
|
|
||||||
index: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state: 'colorfade',
|
|
||||||
duration: 15000,
|
|
||||||
delay: 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if (tx.view.state === 'pool' || tx.view.state === 'colorfade') {
|
|
||||||
// this.updateTx(tx, {
|
|
||||||
// display: {
|
|
||||||
// layer: this.layer
|
|
||||||
// },
|
|
||||||
// duration: 2000,
|
|
||||||
// delay: 0,
|
|
||||||
// state: 'pause',
|
|
||||||
// next: shuffleUpdate
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
this.updateTx(tx, shuffleUpdate)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutAll () {
|
layoutAll () {
|
||||||
this.scene.count = 0
|
this.scene.count = 0
|
||||||
|
this.poolTop = Infinity
|
||||||
|
this.poolBottom = -Infinity
|
||||||
let ids = this.getHiddenTxList()
|
let ids = this.getHiddenTxList()
|
||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
this.txs[ids[i]] = this.hiddenTxs[ids[i]]
|
this.txs[ids[i]] = this.hiddenTxs[ids[i]]
|
||||||
@ -175,6 +198,14 @@ export default class TxPoolScene {
|
|||||||
for (let i = 0; i < ids.length; i++) {
|
for (let i = 0; i < ids.length; i++) {
|
||||||
this.layoutTx(this.txs[ids[i]], this.scene.count++)
|
this.layoutTx(this.txs[ids[i]], this.scene.count++)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.heightLimit && ((this.poolTop + this.scene.scroll) < this.heightBound)) {
|
||||||
|
let scrollAmount = (this.poolTop + this.scene.scroll) - this.heightBound
|
||||||
|
this.scroll(scrollAmount, true)
|
||||||
|
} else if (this.heightLimit && ((this.poolTop + this.scene.scroll) > this.heightBound)) {
|
||||||
|
let scrollAmount = Math.min(this.scene.scroll, (this.poolTop + this.scene.scroll) - this.heightBound)
|
||||||
|
this.scroll(scrollAmount, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remove (id) {
|
remove (id) {
|
||||||
@ -201,10 +232,16 @@ export default class TxPoolScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
place (id, position) {
|
place (id, position) {
|
||||||
return {
|
const placement = {
|
||||||
x: this.scene.offset.x + (this.paddedUnitWidth * (1 + Math.floor(position % this.blockWidth))),
|
x: this.scene.offset.x + (this.paddedUnitWidth * (1 + Math.floor(position % this.blockWidth))),
|
||||||
y: this.scene.offset.y + this.height - (this.paddedUnitWidth * (0.5 + (Math.floor(position / this.blockWidth)))) + this.scene.scroll
|
y: this.scene.offset.y + this.height - (this.paddedUnitWidth * (1 + (Math.floor(position / this.blockWidth))))
|
||||||
}
|
}
|
||||||
|
if (placement.y < this.poolTop) {
|
||||||
|
this.poolTop = placement.y
|
||||||
|
} else if (placement.y > this.poolBottom) {
|
||||||
|
this.poolBottom = placement.y
|
||||||
|
}
|
||||||
|
return placement
|
||||||
}
|
}
|
||||||
|
|
||||||
getVertexData () {
|
getVertexData () {
|
||||||
|
@ -1,105 +1,132 @@
|
|||||||
function interpolateAnimationState (from, to, progress) {
|
function interpolateAttributeStart(attribute, now, label) {
|
||||||
const clampedProgress = Math.max(0,Math.min(1,progress))
|
if (attribute.v == 0 || (attribute.t + attribute.d) <= now) {
|
||||||
return Object.keys(from).reduce((result, key) => {
|
// transition finished, next transition starts from current end state
|
||||||
if (to[key] != null) {
|
// (clamp to 1)
|
||||||
result[key] = from[key] + ((to[key] - from[key]) * clampedProgress)
|
attribute.a = attribute.b
|
||||||
}
|
attribute.v = 0
|
||||||
return result
|
attribute.d = 0
|
||||||
}, from)
|
} else if (attribute.t > now) {
|
||||||
|
// transition not started
|
||||||
|
// (clamp to 0)
|
||||||
|
} else {
|
||||||
|
// transition in progress
|
||||||
|
// (interpolate)
|
||||||
|
let progress = (now - attribute.t)
|
||||||
|
attribute.a = attribute.a + ((progress / attribute.d) * (attribute.b - attribute.a))
|
||||||
|
attribute.d = attribute.d - progress
|
||||||
|
attribute.v = 1 / attribute.d
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TxSprite {
|
export default class TxSprite {
|
||||||
constructor({ now, id, value, layer, position, size, palette, color, alpha }) {
|
constructor({ now = Date.now(), id, value, layer, position, size, palette, color, alpha }, vertexArray) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.value = value
|
this.value = value
|
||||||
this.layer = layer
|
this.layer = layer
|
||||||
|
this.vertexArray = vertexArray
|
||||||
|
|
||||||
this.duration = 0
|
this.attributes = {
|
||||||
this.v = 0
|
x: { a: position.x, b: position.x, t: now, v: 0, d: 0 },
|
||||||
this.start = now
|
y: { a: position.y, b: position.y, t: now, v: 0, d: 0 },
|
||||||
|
r: { a: size, b: size, t: now, v: 0, d: 0 },
|
||||||
|
p: { a: palette, b: palette, t: now, v: 0, d: 0 },
|
||||||
|
c: { a: color, b: color, t: now, v: 0, d: 0 },
|
||||||
|
a: { a: alpha, b: alpha, t: now, v: 0, d: 0 },
|
||||||
|
}
|
||||||
|
|
||||||
this.from = {
|
this.vertexPointer = this.vertexArray.insert(this)
|
||||||
x: position.x,
|
|
||||||
y: position.y,
|
this.compile()
|
||||||
|
}
|
||||||
|
|
||||||
|
update({ now, layer, position, size, palette, color, alpha, duration, minDuration, adjust }) {
|
||||||
|
const v = duration > 0 ? (1 / duration) : 0
|
||||||
|
|
||||||
|
let update = {
|
||||||
|
x: position ? position.x : null,
|
||||||
|
y: position ? position.y: null,
|
||||||
r: size,
|
r: size,
|
||||||
p: palette,
|
p: palette,
|
||||||
c: color,
|
c: color,
|
||||||
a: alpha
|
a: alpha
|
||||||
}
|
}
|
||||||
this.to = {
|
|
||||||
...this.from
|
|
||||||
}
|
|
||||||
|
|
||||||
this.compile()
|
for (const key of Object.keys(update)) {
|
||||||
}
|
// for each non-null attribute:
|
||||||
|
if (update[key] != null) {
|
||||||
update({ now, duration, layer, position, size, palette, color, alpha }) {
|
// calculate current interpolated value, and set as 'from'
|
||||||
// Save a copy of the current target display
|
interpolateAttributeStart(this.attributes[key], now, key)
|
||||||
const currentTarget = this.to
|
// set 'start' to now
|
||||||
|
this.attributes[key].t = now
|
||||||
// Check if we're mid-transition
|
// if 'adjust' flag set
|
||||||
const progress = (this.duration && this.start) ? ((now - this.start) / this.duration) : 0
|
// set 'duration' to Max(remaining time, 'duration')
|
||||||
if (progress >= 1 || progress <= 0) {
|
if (!adjust || (duration && this.attributes[key].d == 0)) {
|
||||||
// Transition finished or hasn't started:
|
this.attributes[key].v = v
|
||||||
// so next transition starts from the current display state
|
this.attributes[key].d = duration
|
||||||
this.from = {
|
} else if (minDuration > this.attributes[key].d) {
|
||||||
...currentTarget
|
this.attributes[key].v = 1 / minDuration
|
||||||
|
this.attributes[key].d = minDuration
|
||||||
|
}
|
||||||
|
// set 'to' to target value
|
||||||
|
this.attributes[key].b = update[key]
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Mid-transition:
|
|
||||||
// we want to start the next transition from our current intermediate state
|
|
||||||
// so find that by interpolating between the last transitions start and end states
|
|
||||||
this.from = interpolateAnimationState(this.from, currentTarget, progress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build new target display, inheriting from the current target where necessary
|
|
||||||
this.to = {
|
|
||||||
x: (position && position.x != null) ? position.x : this.from.x,
|
|
||||||
y: (position && position.y != null) ? position.y : this.from.y,
|
|
||||||
r: (size != null) ? size : this.from.r,
|
|
||||||
p: (palette != null) ? palette : this.from.p,
|
|
||||||
c: (color != null) ? color : this.from.c,
|
|
||||||
a: (alpha != null) ? alpha : this.from.a
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layer != null) this.layer = layer
|
|
||||||
if (duration != null) {
|
|
||||||
this.duration = duration
|
|
||||||
this.v = duration ? (1 / duration) : 0
|
|
||||||
}
|
|
||||||
this.start = now
|
|
||||||
|
|
||||||
this.compile()
|
this.compile()
|
||||||
}
|
}
|
||||||
|
|
||||||
compile () {
|
compile () {
|
||||||
this.vertexData = [
|
this.vertexData = [
|
||||||
this.start,
|
this.attributes.x.a,
|
||||||
this.layer,
|
this.attributes.x.b,
|
||||||
this.v,
|
this.attributes.x.t,
|
||||||
this.from.x,
|
this.attributes.x.v,
|
||||||
this.from.y,
|
|
||||||
this.to.x,
|
this.attributes.y.a,
|
||||||
this.to.y,
|
this.attributes.y.b,
|
||||||
this.from.r,
|
this.attributes.y.t,
|
||||||
this.to.r,
|
this.attributes.y.v,
|
||||||
this.from.p,
|
|
||||||
this.to.p,
|
this.attributes.r.a,
|
||||||
this.from.c,
|
this.attributes.r.b,
|
||||||
this.to.c,
|
this.attributes.r.t,
|
||||||
this.from.a,
|
this.attributes.r.v,
|
||||||
this.to.a,
|
|
||||||
|
this.attributes.p.a,
|
||||||
|
this.attributes.p.b,
|
||||||
|
this.attributes.p.t,
|
||||||
|
this.attributes.p.v,
|
||||||
|
|
||||||
|
this.attributes.c.a,
|
||||||
|
this.attributes.c.b,
|
||||||
|
this.attributes.c.t,
|
||||||
|
this.attributes.c.v,
|
||||||
|
|
||||||
|
this.attributes.a.a,
|
||||||
|
this.attributes.a.b,
|
||||||
|
this.attributes.a.t,
|
||||||
|
this.attributes.a.v,
|
||||||
]
|
]
|
||||||
|
this.vertexArray.setData(this.vertexPointer, this.vertexData)
|
||||||
|
}
|
||||||
|
|
||||||
|
moveVertexPointer (index) {
|
||||||
|
this.vertexPointer = index
|
||||||
}
|
}
|
||||||
|
|
||||||
getPosition () {
|
getPosition () {
|
||||||
return {
|
return {
|
||||||
x: this.to.x,
|
x: this.attributes.x.b,
|
||||||
y: this.to.y
|
y: this.attributes.y.b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getVertexData () {
|
getVertexData () {
|
||||||
return this.vertexData
|
return this.vertexData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.vertexArray.remove(this.vertexPointer)
|
||||||
|
this.vertexPointer = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,28 @@ import TxSprite from './TxSprite.js'
|
|||||||
import { timeOffset } from '../utils/time.js'
|
import { timeOffset } from '../utils/time.js'
|
||||||
|
|
||||||
// converts from this class's update format to TxSprite's update format
|
// converts from this class's update format to TxSprite's update format
|
||||||
function toSpriteUpdate(display, duration, start) {
|
// now, id, value, layer, position, size, palette, color, alpha, duration, adjust
|
||||||
|
function toSpriteUpdate(display, duration, start, adjust) {
|
||||||
return {
|
return {
|
||||||
now: start ? start - timeOffset : Date.now() - timeOffset,
|
now: start ? start - timeOffset : Date.now() - timeOffset,
|
||||||
duration,
|
duration: duration,
|
||||||
...display,
|
...display,
|
||||||
...(display.color ? { palette: display.color.palette, color: display.color.index, alpha: display.color.alpha } : { color: null })
|
...(display.color ? { palette: display.color.palette, color: display.color.index, alpha: display.color.alpha } : { color: null }),
|
||||||
|
adjust
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TxView {
|
export default class TxView {
|
||||||
constructor ({ id, time, value }) {
|
constructor ({ id, time, value, vertexArray }) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.time = time
|
this.time = time
|
||||||
this.value = value
|
this.value = value
|
||||||
this.initialised = false
|
this.initialised = false
|
||||||
|
this.vertexArray = vertexArray
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
if (this.sprite) this.sprite.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -30,28 +37,18 @@ export default class TxView {
|
|||||||
duration: of the tweening animation from the previous display state
|
duration: of the tweening animation from the previous display state
|
||||||
delay: for queued transitions, how long to wait after current transition
|
delay: for queued transitions, how long to wait after current transition
|
||||||
completes to start.
|
completes to start.
|
||||||
next: another transition to animate after this one completes
|
|
||||||
(you can nest several transitions like this)
|
|
||||||
Performing another direct update cancels any pending transitions
|
|
||||||
*/
|
*/
|
||||||
update ({ display, duration, delay, state, next, start }) {
|
update ({ display, duration, delay, state, start, adjust }) {
|
||||||
if (next) {
|
|
||||||
if (this.awaiting) clearTimeout(this.awaiting)
|
|
||||||
this.awaiting = setTimeout(() => {
|
|
||||||
this.update(next)
|
|
||||||
}, (duration || 0) + (next.delay || 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = state
|
this.state = state
|
||||||
|
|
||||||
if (!this.initialised) {
|
if (!this.initialised || !this.sprite) {
|
||||||
this.initialised = true
|
this.initialised = true
|
||||||
const update = toSpriteUpdate(display, duration, start)
|
const update = toSpriteUpdate(display, duration, start)
|
||||||
update.id = this.id
|
update.id = this.id
|
||||||
update.value = this.value
|
update.value = this.value
|
||||||
this.sprite = new TxSprite(update)
|
this.sprite = new TxSprite(update, this.vertexArray)
|
||||||
} else {
|
} else {
|
||||||
const update = toSpriteUpdate(display, duration, start)
|
const update = toSpriteUpdate(display, duration, start, adjust)
|
||||||
this.sprite.update(update)
|
this.sprite.update(update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
varying lowp vec4 vColor;
|
varying lowp vec4 vColor;
|
||||||
|
|
||||||
attribute float startTime;
|
// each attribute contains [x: startValue, y: endValue, z: startTime, w: rate]
|
||||||
attribute float zIndex;
|
// shader interpolates between start and end values at the given rate, from the given time
|
||||||
attribute float speed;
|
attribute vec4 posX;
|
||||||
attribute vec4 positions;
|
attribute vec4 posY;
|
||||||
attribute vec2 sizes;
|
attribute vec4 sizes;
|
||||||
attribute vec2 colors;
|
attribute vec4 colors;
|
||||||
attribute vec2 palettes;
|
attribute vec4 palettes;
|
||||||
attribute vec2 alphas;
|
attribute vec4 alphas;
|
||||||
|
|
||||||
uniform vec2 screenSize;
|
uniform vec2 screenSize;
|
||||||
uniform float now;
|
uniform float now;
|
||||||
// uniform float opacityTarget; // target opacity for fading points
|
|
||||||
uniform sampler2D colorTexture;
|
uniform sampler2D colorTexture;
|
||||||
|
|
||||||
vec3 selectPalette(float index) {
|
vec3 selectPalette(float index) {
|
||||||
@ -22,17 +21,20 @@ vec3 selectPalette(float index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float interpolateAttribute(vec4 attr) {
|
||||||
|
float delta = clamp((now - attr.z) * attr.w, 0.0, 1.0);
|
||||||
|
return mix(attr.x, attr.y, delta);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 screenTransform = vec4(2.0 / screenSize.x, -2.0 / screenSize.y, -1.0, 1.0);
|
vec4 screenTransform = vec4(2.0 / screenSize.x, -2.0 / screenSize.y, -1.0, 1.0);
|
||||||
|
|
||||||
float delta = clamp((now - startTime) * speed, 0.0, 1.0);
|
vec2 position = vec2(interpolateAttribute(posX), interpolateAttribute(posY));
|
||||||
|
gl_PointSize = interpolateAttribute(sizes);
|
||||||
|
gl_Position = vec4(position * screenTransform.xy + screenTransform.zw, 1.0, 1.0);
|
||||||
|
|
||||||
vec2 position = mix(positions.xy, positions.zw, delta);
|
float colorIndex = interpolateAttribute(colors);
|
||||||
gl_PointSize = mix(sizes.x, sizes.y, delta);
|
float alpha = interpolateAttribute(alphas);
|
||||||
gl_Position = vec4(position * screenTransform.xy + screenTransform.zw, zIndex, 1.0);
|
|
||||||
|
|
||||||
float colorIndex = mix(colors.x, colors.y, delta);
|
|
||||||
float alpha = mix(alphas.x, alphas.y, delta);
|
|
||||||
if (palettes.y < 1.0) { // texture color
|
if (palettes.y < 1.0) { // texture color
|
||||||
vec4 texel = texture2D(colorTexture, vec2(colorIndex, 0.0));
|
vec4 texel = texture2D(colorTexture, vec2(colorIndex, 0.0));
|
||||||
vColor = vec4(texel.rgb, alpha);
|
vColor = vec4(texel.rgb, alpha);
|
||||||
|
@ -296,3 +296,4 @@ function createCounter () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const txQueueLength = createCounter()
|
export const txQueueLength = createCounter()
|
||||||
|
export const txCount = createCounter()
|
||||||
|
112
src/utils/memory.js
Normal file
112
src/utils/memory.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { txCount } from '../stores.js'
|
||||||
|
|
||||||
|
/*
|
||||||
|
Utility class for access and management of low-level sprite data
|
||||||
|
|
||||||
|
Maintains a single Float32Array of sprite data, keeping track of empty slots
|
||||||
|
to allow constant-time insertion and deletion
|
||||||
|
|
||||||
|
Automatically resizes by copying to a new, larger Float32Array when necessary,
|
||||||
|
or compacting into a smaller Float32Array when there's space to do so.
|
||||||
|
*/
|
||||||
|
export class FastVertexArray {
|
||||||
|
constructor (length, stride) {
|
||||||
|
// console.log(`Creating Fast Vertex Array with length ${length} and stride ${stride} `)
|
||||||
|
this.length = length
|
||||||
|
this.count = 0
|
||||||
|
this.stride = stride
|
||||||
|
this.sprites = []
|
||||||
|
this.data = new Float32Array(this.length * this.stride)
|
||||||
|
this.freeSlots = []
|
||||||
|
this.lastSlot = 0
|
||||||
|
this.nullSprite = new Float32Array(this.stride)
|
||||||
|
// this.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
print () {
|
||||||
|
// console.log(`Length: ${this.length}, Free slots: ${this.freeSlots.length}, last slot: ${this.lastSlot}`)
|
||||||
|
// console.log(this.freeSlots)
|
||||||
|
// console.log(this.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
insert (sprite) {
|
||||||
|
// console.log('inserting into FVA')
|
||||||
|
this.count++
|
||||||
|
txCount.increment()
|
||||||
|
|
||||||
|
let position
|
||||||
|
if (this.freeSlots.length) {
|
||||||
|
position = this.freeSlots.shift()
|
||||||
|
} else {
|
||||||
|
position = this.lastSlot
|
||||||
|
this.lastSlot++
|
||||||
|
if (this.lastSlot > this.length) {
|
||||||
|
this.expand()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this.print()
|
||||||
|
this.sprites[position] = sprite
|
||||||
|
return position
|
||||||
|
}
|
||||||
|
|
||||||
|
remove (index) {
|
||||||
|
this.count--
|
||||||
|
txCount.decrement()
|
||||||
|
this.setData(index, this.nullSprite)
|
||||||
|
this.freeSlots.push(index)
|
||||||
|
this.sprites[index] = null
|
||||||
|
if (this.length > 2048 && this.count < (this.length * 0.4)) this.compact()
|
||||||
|
// this.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
setData (index, dataChunk) {
|
||||||
|
// console.log(`Updating chunk at ${index} (${index * this.stride})`)
|
||||||
|
this.data.set(dataChunk, (index * this.stride))
|
||||||
|
// this.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
getData (index) {
|
||||||
|
return this.data.subarray(index, this.stride)
|
||||||
|
}
|
||||||
|
|
||||||
|
expand () {
|
||||||
|
// console.log('Expanding FVA')
|
||||||
|
this.length *= 2
|
||||||
|
const newData = new Float32Array(this.length * this.stride)
|
||||||
|
newData.set(this.data)
|
||||||
|
this.data = newData
|
||||||
|
this.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
compact () {
|
||||||
|
// console.log('Compacting FVA')
|
||||||
|
// console.log(this.sprites)
|
||||||
|
// New array length is the smallest power of 2 larger than the sprite count
|
||||||
|
const newLength = Math.pow(2, Math.ceil(Math.log2(this.count)))
|
||||||
|
if (this.newLength != this.length) {
|
||||||
|
// console.log(`compacting from ${this.length} to ${newLength}`)
|
||||||
|
this.length = newLength
|
||||||
|
this.data = new Float32Array(this.length * this.stride)
|
||||||
|
let sprite
|
||||||
|
const newSprites = []
|
||||||
|
let i = 0
|
||||||
|
for (var index in this.sprites) {
|
||||||
|
sprite = this.sprites[index]
|
||||||
|
if (sprite) {
|
||||||
|
newSprites.push(sprite)
|
||||||
|
sprite.moveVertexPointer(i)
|
||||||
|
sprite.compile()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sprites = newSprites
|
||||||
|
this.freeSlots = []
|
||||||
|
this.lastSlot = i
|
||||||
|
}
|
||||||
|
this.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
getVertexData () {
|
||||||
|
return this.data
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user