@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
270 lines (204 loc) • 7 kB
JavaScript
import { assert } from "../../../core/assert.js";
import List from "../../../core/collection/list/List.js";
import { noop } from "../../../core/function/noop.js";
import { SerializationMetadata } from "../../ecs/components/SerializationMetadata.js";
import Entity from "../../ecs/Entity.js";
import { EventType } from "../../ecs/EventType.js";
import GUIElement from "../../ecs/gui/GUIElement.js";
import NotificationLog from "../../notify/NotificationLog.js";
class LogDisplay {
/**
*
* @param {NotificationLog} log
* @param {function(log:NotificationLog)} initializer
* @param {function(timeDelta:number)} [updateFunction]
*/
constructor({
log,
initializer,
updateFunction = noop
}) {
this.needsUpdate = updateFunction !== noop;
this.__update_function = updateFunction;
this.log = log;
this.initializer = initializer;
/**
* Allows modifying timing of the view emitter by slowing or speeding up the time
* @type {number}
*/
this.timeScale = 1;
}
/**
*
* @param {number} timeDelta
*/
update(timeDelta) {
const td = this.timeScale * timeDelta;
this.__update_function(td);
}
initialize() {
this.initializer(this.log);
}
}
export class NotificationManager {
constructor() {
/**
*
* @type {Map<string, NotificationLog>}
*/
this.channels = new Map();
/**
*
* @type {List<LogDisplay>}
*/
this.displays = new List();
/**
*
* @type {string}
* @private
*/
this.defaultArea = undefined;
/**
*
* @type {EntityComponentDataset|null}
*/
this.ecd = null;
}
/**
*
* @param {string} channel
*/
createChannel(channel) {
assert.isString(channel, 'channel');
if (this.channels.has(channel)) {
throw new Error(`channel '${channel}' already exists`);
}
this.channels.set(channel, new NotificationLog());
if (this.defaultArea === undefined) {
this.defaultArea = channel;
}
}
/**
*
* @param {string} channel
* @returns {NotificationLog|undefined}
*/
getChannel(channel) {
return this.channels.get(channel);
}
/**
*
* @param {string} channel
* @returns {LogDisplay[]}
*/
getDisplays(channel) {
const log = this.getChannel(channel);
return this.displays.filter(d => d.log === log);
}
/**
*
* @param {string} channel
* @param {function(log:NotificationLog):View} initializer
* @param {function(timeDelta:number)} [updateFunction]
*/
addDisplay(channel, initializer, updateFunction) {
assert.isString(channel, 'channel');
assert.isFunction(initializer, 'initializer');
const log = this.channels.get(channel);
if (log === undefined) {
throw new Error(`channel '${channel}' doesn't exist`);
}
const display = new LogDisplay({ log, initializer, updateFunction });
this.displays.add(display);
display.initialize();
}
/**
*
* @param {string} channel
* @param {ViewEmitter} viewEmitter
* @param {String} [grouping]
*/
addEmitterDisplay(channel, viewEmitter, grouping = null) {
/**
*
* @type {Map<View, Entity>}
*/
const views = new Map();
const self = this;
const managedNotificationChannelClass = `managed-notification-channel-${channel}`;
function handleEntityRemoved(entity) {
//find existing entry
for (const [view, builder] of views) {
if (builder.id === entity) {
//clear from the map
views.delete(view);
break;
}
}
}
function viewFactory(log) {
viewEmitter.objectEmitter.objectFinalizer = function (view) {
const entity = views.get(view);
if (entity !== undefined) {
entity.removeEventListener(EventType.EntityRemoved, handleEntityRemoved);
entity.destroy();
//remove from the map
views.delete(view);
}
};
log.elements.on.added.add(function (notification) {
assert.notEqual(notification, null, 'notification is null');
assert.notEqual(notification, undefined, 'notification is undefined');
viewEmitter.spawn(notification);
});
viewEmitter.on.spanwed.add(function (view) {
view.addClass(managedNotificationChannelClass);
const eb = new Entity();
//prevent serialization of the notification
eb.add(SerializationMetadata.Transient);
const ecd = self.ecd;
if (ecd !== null) {
const guiElement = GUIElement.fromView(view);
guiElement.group = grouping;
eb.addEventListener(EventType.EntityRemoved, handleEntityRemoved);
eb.add(guiElement)
.build(ecd);
//console.warn('Notification View Spawned:', eb);
views.set(view, eb);
}
});
}
function updateFunction(tileDelta) {
viewEmitter.tick(tileDelta);
}
this.addDisplay(channel, viewFactory, updateFunction);
}
/**
*
* @param {Notification} notification
* @param {string} [areaId] uses default area when not specified
*/
addNotification(notification, areaId = this.defaultArea) {
assert.notEqual(notification, undefined, 'notification is undefined');
assert.notEqual(notification, null, 'notification is null');
const channel = this.channels.get(areaId);
if (channel === undefined) {
throw new Error(`Area '${areaId}' doesn't exist`);
}
channel.addNotification(notification);
}
/**
*
* @param {number} timeDelta
*/
tick(timeDelta) {
const displays = this.displays;
const n = displays.length;
for (let i = 0; i < n; i++) {
const display = displays.get(i);
if (display.needsUpdate) {
display.update(timeDelta);
}
}
}
}