Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/asset/core/channel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/** @import { AssetId } from '../types/index.js' */

/**
* @readonly
* @enum {number}
*/
export const AssetChannelMessageType = {
Acquire: 0,
Release: 1
}

/**
* @template T
*/
export class AssetChannel {

/**
* @private
* @type {AssetChannelMessage<T>[]}
*/
queue = []

/**
* Queue a reference acquisition.
*
* @param {AssetId} assetId
*/
acquire(assetId) {
this.queue.push(new AssetChannelMessage(AssetChannelMessageType.Acquire, assetId))
}

/**
* Queue a reference release.
*
* @param {AssetId} assetId
*/
release(assetId) {
this.queue.push(new AssetChannelMessage(AssetChannelMessageType.Release, assetId))
}

/**
* Drain the queued messages.
*
* @returns {Readonly<AssetChannelMessage<T>[]>}
*/
flush() {
const { queue } = this

if (queue.length) this.queue = []

return queue
}
}

/**
* @template T
*/
export class AssetChannelMessage {

/**
* @readonly
* @type {AssetChannelMessageType}
*/
type

/**
* @readonly
* @type {AssetId}
*/
assetId

/**
* @param {AssetChannelMessageType} type
* @param {AssetId} assetId
*/
constructor(type, assetId) {
this.type = type
this.assetId = assetId
}
}
157 changes: 157 additions & 0 deletions src/asset/core/handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/** @import {AssetId} from '../types/index.js' */
/** @import {Assets} from '../resources/assets.js' */
/** @import {Constructor} from '../../type/index.js'*/

import { packInto64Int } from '../../algorithms/index.js'
import { typeid } from '../../type/index.js'
import { AssetServer } from '../resources/assetserver.js'
import { AssetChannel } from './channel.js'

/**
* @template T
*/
export class Handle {

/**
* @readonly
* @type {Constructor<T>}
*/
type

/**
* @private
* @type {boolean}
*/
dropped = false

/**
* @private
* @readonly
* @type {AssetChannel<T>}
*/
channel

/**
* @readonly
* @type {number}
*/
index

/**
* @readonly
* @type {number}
*/
generation = 0

/**
* @param {AssetChannel<T>} channel
* @param {Constructor<T>} type
* @param {number} index
* @param {number} generation
*/
constructor(channel, type, index, generation) {
this.index = index
this.generation = generation
this.channel = channel
this.type = type
}

/**
* @returns {AssetId}
*/
id() {
return /** @type {AssetId}*/ (packInto64Int(this.index, this.generation))
}

clone() {
const { channel, index, generation } = this

channel.acquire(this.id())

return new Handle(channel, this.type, index, generation)
}

/**
* Snapshot the handle with the asset server path when available.
*
* @param {import('../../ecs/index.js').World} world
* @returns {HandleSnapshot<T>}
*/
toSnapshot(world) {
const server = world.getResource(AssetServer)
const info = server?.getAssetInfo(this)

if (info?.path) {
return new HandleSnapshot(this.type, info.path)
}

return new HandleSnapshot(this.type, this.id())
}

drop() {
if (this.dropped) return

this.channel.release(this.id())
this.dropped = true
}
}

/**
* A snapshot of an asset handle.
*
* The snapshot preserves the asset type and id, and stores the asset server
* path when one is registered so the handle can be reloaded by path.
*
* @template T
*/
export class HandleSnapshot {

/**
* @readonly
* @type {Constructor<T>}
*/
type

/**
* @readonly
* @type {AssetId | string}
*/
asset

/**
* @param {Constructor<T>} type
* @param {AssetId | string} asset
*/
constructor(type, asset) {
this.type = type
this.asset = asset
}

/**
* Restore the live handle from the asset server.
*
* If the asset server knows the path, we reload it by path. Otherwise we
* upgrade the stored asset id against the asset pool.
*
* @param {import('../../ecs/index.js').World} world
* @returns {Handle<T>}
*/
fromSnapshot(world) {
const server = world.getResource(AssetServer)

if (typeof this.asset === 'string') {
return /** @type {Handle<T>} */ (server.load(this.type, this.asset))
}

const assets = /** @type {Assets<T>} */ (server.getAssets(typeid(this.type)))

// TODO: This is inherently incorrect. When scene resources are added,
// the assetid will point to the wrong asset in the scene due to desync between
// the scene and world when an assets are added/removed from the world or scene.
// Add a mapping between scene assets and world assets and use that to create
// the asset handle. Also ensure the assets are loaded into world before spawning
// the scene into the world.

return /** @type {Handle<T>} */ (assets.upgrade(this.asset))
}
}
3 changes: 2 additions & 1 deletion src/asset/core/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './asset.js'
export * from './channel.js'
export * from './handle.js'
export * from './exporter.js'
export * from './importer.js'
11 changes: 9 additions & 2 deletions src/asset/plugins/asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { App, Plugin } from '../../app/index.js'
import { AppSchedule, CoreSystems } from '../../core/index.js'
import { EventPlugin } from '../../event/index.js'
import { typeid, typeidGeneric } from '../../type/index.js'
import { Assets } from '../core/index.js'
import { Assets } from '../resources/index.js'
import { AssetAdded, AssetDropped, AssetModified } from '../events/index.js'
import { registerAssetTypes, registerAssetOnAssetServer, unloadDroppedAssets, updateAssetEvents } from '../systems/index.js'
import { registerAssetTypes, registerAssetOnAssetServer, unloadDroppedAssets, updateAssetChannel, updateAssetEvents } from '../systems/index.js'

/**
* @template T
Expand Down Expand Up @@ -44,6 +44,13 @@ export class AssetPlugin extends Plugin {
const { asset, events } = this
const world = app.getWorld()

app.registerSystem({
label: `updateAssetChannel<${typeid(asset)}>`,
schedule: AppSchedule.Update,
systemGroup: CoreSystems.End,
system: updateAssetChannel(asset)
})

if (events) {
app
.registerPlugin(new EventPlugin({
Expand Down
Loading
Loading