added resource culling and resource saving in the ResourceManager

This commit is contained in:
Joe Weaver 2021-03-19 15:44:11 -04:00
parent 460d0e3643
commit b085612908
17 changed files with 316 additions and 60 deletions

View File

@ -1,6 +1,5 @@
import AI from "../DataTypes/Interfaces/AI"; import AI from "../DataTypes/Interfaces/AI";
import StateMachine from "../DataTypes/State/StateMachine"; import StateMachine from "../DataTypes/State/StateMachine";
import GameEvent from "../Events/GameEvent";
import GameNode from "../Nodes/GameNode"; import GameNode from "../Nodes/GameNode";
/** /**
@ -17,6 +16,7 @@ export default class StateMachineAI extends StateMachine implements AI {
destroy(){ destroy(){
// Get rid of our reference to the owner // Get rid of our reference to the owner
delete this.owner; delete this.owner;
this.receiver.destroy();
} }
// @implemented // @implemented

View File

@ -13,6 +13,9 @@ export default interface Physical {
/** Represents whether the object is moving or not. */ /** Represents whether the object is moving or not. */
moving: boolean; moving: boolean;
/** Represent whether the object is frozen from moving or not. */
frozen: boolean;
/** Represents whether the object is on the ground or not. */ /** Represents whether the object is on the ground or not. */
onGround: boolean; onGround: boolean;

View File

@ -37,7 +37,7 @@ export default class Queue<T> implements Collection {
*/ */
enqueue(item: T): void{ enqueue(item: T): void{
if((this.tail + 1) % this.MAX_ELEMENTS === this.head){ if((this.tail + 1) % this.MAX_ELEMENTS === this.head){
throw "Queue full - cannot add element" throw new Error("Queue full - cannot add element");
} }
this.size += 1; this.size += 1;
@ -51,7 +51,7 @@ export default class Queue<T> implements Collection {
*/ */
dequeue(): T { dequeue(): T {
if(this.head === this.tail){ if(this.head === this.tail){
throw "Queue empty - cannot remove element" throw new Error("Queue empty - cannot remove element");
} }

View File

@ -5,7 +5,7 @@ import Collection from "./Collection";
*/ */
export default class Stack<T> implements Collection { export default class Stack<T> implements Collection {
/** The maximum number of elements in the Stack */ /** The maximum number of elements in the Stack */
private readonly MAX_ELEMENTS: number; private MAX_ELEMENTS: number;
/** The internal representation of the stack */ /** The internal representation of the stack */
private stack: Array<T>; private stack: Array<T>;

View File

@ -81,7 +81,7 @@ export default class EventQueue {
unsubscribe(receiver: Receiver, ...events: Array<string>): void { unsubscribe(receiver: Receiver, ...events: Array<string>): void {
this.receivers.forEach(eventName => { this.receivers.forEach(eventName => {
// If keys were provided, only continue if this key is one of them // If keys were provided, only continue if this key is one of them
if(events !== undefined && events.indexOf(eventName) === -1) return; if(events.length > 0 && events.indexOf(eventName) === -1) return;
// Find the index of our receiver for this key // Find the index of our receiver for this key
let index = this.receivers.get(eventName).indexOf(receiver); let index = this.receivers.get(eventName).indexOf(receiver);

View File

@ -36,7 +36,12 @@ export default class Receiver {
* @param event The event to receive * @param event The event to receive
*/ */
receive(event: GameEvent): void { receive(event: GameEvent): void {
try{
this.q.enqueue(event); this.q.enqueue(event);
} catch(e){
console.warn("Receiver overflow for event " + event.toString());
throw e;
}
} }
/** /**

View File

@ -33,6 +33,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
/*---------- PHYSICAL ----------*/ /*---------- PHYSICAL ----------*/
hasPhysics: boolean = false; hasPhysics: boolean = false;
moving: boolean = false; moving: boolean = false;
frozen: boolean = false;
onGround: boolean = false; onGround: boolean = false;
onWall: boolean = false; onWall: boolean = false;
onCeiling: boolean = false; onCeiling: boolean = false;
@ -151,11 +152,13 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
* @param velocity The velocity with which to move the object. * @param velocity The velocity with which to move the object.
*/ */
move(velocity: Vec2): void { move(velocity: Vec2): void {
if(this.frozen) return;
this.moving = true; this.moving = true;
this._velocity = velocity; this._velocity = velocity;
}; };
moveOnPath(speed: number, path: NavigationPath): void { moveOnPath(speed: number, path: NavigationPath): void {
if(this.frozen) return;
this.path = path; this.path = path;
let dir = path.getMoveDirection(this); let dir = path.getMoveDirection(this);
this.moving = true; this.moving = true;
@ -230,6 +233,9 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
/** Removes this object from the physics system */ /** Removes this object from the physics system */
removePhysics(): void { removePhysics(): void {
// Remove this from the physics manager
this.scene.getPhysicsManager().deregisterObject(this);
// Nullify all physics fields // Nullify all physics fields
this.hasPhysics = false; this.hasPhysics = false;
this.moving = false; this.moving = false;
@ -250,9 +256,16 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
this.collisionShape = null; this.collisionShape = null;
this.colliderOffset = Vec2.ZERO; this.colliderOffset = Vec2.ZERO;
this.sweptRect = null; this.sweptRect = null;
}
// Remove this from the physics manager /** Disables physics movement for this node */
this.scene.getPhysicsManager().deregisterObject(this); freeze(): void {
this.frozen = true;
}
/** Reenables physics movement for this node */
unfreeze(): void {
this.frozen = false;
} }
/** Prevents this object from participating in all collisions and triggers. It can still move. */ /** Prevents this object from participating in all collisions and triggers. It can still move. */

View File

@ -90,7 +90,6 @@ export default abstract class UIElement extends CanvasNode {
} }
if(this.onClickEventId !== null){ if(this.onClickEventId !== null){
let data = {}; let data = {};
console.log("Click event: " + this.onClickEventId)
this.emitter.fireEvent(this.onClickEventId, data); this.emitter.fireEvent(this.onClickEventId, data);
} }
} }

View File

@ -72,7 +72,6 @@ export default class BasicPhysicsManager extends PhysicsManager {
* @param options A record of options * @param options A record of options
*/ */
protected parseOptions(options: Record<string, any>): void { protected parseOptions(options: Record<string, any>): void {
console.log("Parsing physics options: ", options);
if(options.groupNames !== undefined && options.collisions !== undefined){ if(options.groupNames !== undefined && options.collisions !== undefined){
for(let i = 0; i < options.groupNames.length; i++){ for(let i = 0; i < options.groupNames.length; i++){
let group = options.groupNames[i]; let group = options.groupNames[i];
@ -108,6 +107,7 @@ export default class BasicPhysicsManager extends PhysicsManager {
// @override // @override
deregisterObject(node: Physical): void { deregisterObject(node: Physical): void {
console.log("Deregistering physics object");
if(node.isStatic){ if(node.isStatic){
// Remove the node from the static list // Remove the node from the static list
const index = this.staticNodes.indexOf(node); const index = this.staticNodes.indexOf(node);

View File

@ -33,6 +33,10 @@ export default abstract class PhysicsManager implements Updateable {
this.groupNames = new Array(); this.groupNames = new Array();
} }
destroy(): void {
this.receiver.destroy();
}
/** /**
* Registers a gamenode with this physics manager * Registers a gamenode with this physics manager
* @param object The object to register * @param object The object to register

View File

@ -81,7 +81,6 @@ export default class ResourceManager {
private loadonly_gl_ShaderProgramsToLoad: number; private loadonly_gl_ShaderProgramsToLoad: number;
private loadonly_gl_ShaderLoadingQueue: Queue<KeyPath_Shader>; private loadonly_gl_ShaderLoadingQueue: Queue<KeyPath_Shader>;
private gl_DefaultShaderPrograms: Map<WebGLProgramType>;
private gl_ShaderPrograms: Map<WebGLProgramType>; private gl_ShaderPrograms: Map<WebGLProgramType>;
private gl_Textures: Map<number>; private gl_Textures: Map<number>;
@ -90,6 +89,13 @@ export default class ResourceManager {
private gl: WebGLRenderingContext; private gl: WebGLRenderingContext;
/* ########## UNLOADING AND EXCLUSION LIST ########## */
/** A list of resources that will be unloaded at the end of the current scene */
private resourcesToUnload: Array<ResourceReference>;
/** A list of resources to keep until further notice */
private resourcesToKeep: Array<ResourceReference>;
private constructor(){ private constructor(){
this.loading = false; this.loading = false;
this.justLoaded = false; this.justLoaded = false;
@ -123,14 +129,17 @@ export default class ResourceManager {
this.loadonly_gl_ShaderProgramsToLoad = 0; this.loadonly_gl_ShaderProgramsToLoad = 0;
this.loadonly_gl_ShaderLoadingQueue = new Queue(); this.loadonly_gl_ShaderLoadingQueue = new Queue();
this.gl_DefaultShaderPrograms = new Map();
this.gl_ShaderPrograms = new Map(); this.gl_ShaderPrograms = new Map();
this.gl_Textures = new Map(); this.gl_Textures = new Map();
this.gl_NextTextureID = 0; this.gl_NextTextureID = 0;
this.gl_Buffers = new Map(); this.gl_Buffers = new Map();
this.resourcesToUnload = new Array();
this.resourcesToKeep = new Array();
}; };
/* ######################################## SINGLETON ########################################*/
/** /**
* Returns the current instance of this class or a new instance if none exist * Returns the current instance of this class or a new instance if none exist
* @returns The resource manager * @returns The resource manager
@ -143,6 +152,7 @@ export default class ResourceManager {
return this.instance; return this.instance;
} }
/* ######################################## PUBLIC FUNCTION ########################################*/
/** /**
* Activates or deactivates the use of WebGL * Activates or deactivates the use of WebGL
* @param flag True if WebGL should be used, false otherwise * @param flag True if WebGL should be used, false otherwise
@ -165,6 +175,14 @@ export default class ResourceManager {
this.loadonly_imageLoadingQueue.enqueue({key: key, path: path}); this.loadonly_imageLoadingQueue.enqueue({key: key, path: path});
} }
/**
* Tells the resource manager to keep this resource
* @param key The key of the resource
*/
public keepImage(key: string): void {
this.keepResource(key, ResourceType.IMAGE);
}
/** /**
* Retrieves a loaded image * Retrieves a loaded image
* @param key The key of the loaded image * @param key The key of the loaded image
@ -187,6 +205,14 @@ export default class ResourceManager {
this.loadonly_spritesheetLoadingQueue.enqueue({key: key, path: path}); this.loadonly_spritesheetLoadingQueue.enqueue({key: key, path: path});
} }
/**
* Tells the resource manager to keep this resource
* @param key The key of the resource
*/
public keepSpritesheet(key: string): void {
this.keepResource(key, ResourceType.SPRITESHEET);
}
/** /**
* Retrieves a loaded spritesheet * Retrieves a loaded spritesheet
* @param key The key of the spritesheet to load * @param key The key of the spritesheet to load
@ -205,6 +231,14 @@ export default class ResourceManager {
this.loadonly_audioLoadingQueue.enqueue({key: key, path: path}); this.loadonly_audioLoadingQueue.enqueue({key: key, path: path});
} }
/**
* Tells the resource manager to keep this resource
* @param key The key of the resource
*/
public keepAudio(key: string): void {
this.keepResource(key, ResourceType.AUDIO);
}
/** /**
* Retrieves a loaded audio file * Retrieves a loaded audio file
* @param key The key of the audio file to load * @param key The key of the audio file to load
@ -223,6 +257,14 @@ export default class ResourceManager {
this.loadonly_tilemapLoadingQueue.enqueue({key: key, path: path}); this.loadonly_tilemapLoadingQueue.enqueue({key: key, path: path});
} }
/**
* Tells the resource manager to keep this resource
* @param key The key of the resource
*/
public keepTilemap(key: string): void {
this.keepResource(key, ResourceType.TILEMAP);
}
/** /**
* Retreives a loaded tilemap * Retreives a loaded tilemap
* @param key The key of the loaded tilemap * @param key The key of the loaded tilemap
@ -241,6 +283,14 @@ export default class ResourceManager {
this.loadonly_jsonLoadingQueue.enqueue({key: key, path: path}); this.loadonly_jsonLoadingQueue.enqueue({key: key, path: path});
} }
/**
* Tells the resource manager to keep this resource
* @param key The key of the resource
*/
public keepObject(key: string): void {
this.keepResource(key, ResourceType.JSON);
}
/** /**
* Retreives a loaded object * Retreives a loaded object
* @param key The key of the loaded object * @param key The key of the loaded object
@ -250,6 +300,7 @@ export default class ResourceManager {
return this.jsonObjects.get(key); return this.jsonObjects.get(key);
} }
/* ######################################## LOAD FUNCTION ########################################*/
/** /**
* Loads all resources currently in the queue * Loads all resources currently in the queue
* @param callback The function to cal when the resources are finished loading * @param callback The function to cal when the resources are finished loading
@ -293,6 +344,21 @@ export default class ResourceManager {
callback(); callback();
} }
/* ######################################## UNLOAD FUNCTION ########################################*/
private keepResource(key: string, type: ResourceType): void {
console.log("Keep resource...");
for(let i = 0; i < this.resourcesToUnload.length; i++){
let resource = this.resourcesToUnload[i];
if(resource.key === key && resource.resourceType === type){
console.log("Found resource " + key + " of type " + type + ". Keeping.");
let resourceToMove = this.resourcesToUnload.splice(i, 1);
this.resourcesToKeep.push(...resourceToMove);
return;
}
}
}
/** /**
* Deletes references to all resources in the resource manager * Deletes references to all resources in the resource manager
*/ */
@ -300,29 +366,46 @@ export default class ResourceManager {
this.loading = false; this.loading = false;
this.justLoaded = false; this.justLoaded = false;
this.loadonly_imagesLoaded = 0; for(let resource of this.resourcesToUnload){
this.loadonly_imagesToLoad = 0; // Unload the resource
this.images.clear(); this.unloadResource(resource);
}
this.loadonly_spritesheetsLoaded = 0;
this.loadonly_spritesheetsToLoad = 0;
this.spritesheets.clear();
this.loadonly_tilemapsLoaded = 0;
this.loadonly_tilemapsToLoad = 0;
this.tilemaps.clear();
this.loadonly_audioLoaded = 0;
this.loadonly_audioToLoad = 0;
this.audioBuffers.clear();
// WebGL
// Delete all programs through webGL
this.gl_ShaderPrograms.forEach(key => this.gl_ShaderPrograms.get(key).delete(this.gl));
this.gl_ShaderPrograms.clear();
this.gl_Textures.clear();
} }
private unloadResource(resource: ResourceReference): void {
// Delete the resource itself
switch(resource.resourceType){
case ResourceType.IMAGE:
this.images.delete(resource.key);
if(this.gl_WebGLActive){
this.gl_Textures.delete(resource.key);
}
break;
case ResourceType.TILEMAP:
this.tilemaps.delete(resource.key);
break;
case ResourceType.SPRITESHEET:
this.spritesheets.delete(resource.key);
break;
case ResourceType.AUDIO:
this.audioBuffers.delete(resource.key);
break;
case ResourceType.JSON:
this.jsonObjects.delete(resource.key);
break;
case ResourceType.SHADER:
this.gl_ShaderPrograms.get(resource.key).delete(this.gl);
this.gl_ShaderPrograms.delete(resource.key);
break;
}
// Delete any dependencies
for(let dependency of resource.dependencies){
this.unloadResource(dependency);
}
}
/* ######################################## WORK FUNCTIONS ########################################*/
/** /**
* Loads all tilemaps currently in the tilemap loading queue * Loads all tilemaps currently in the tilemap loading queue
* @param onFinishLoading The function to call when loading is complete * @param onFinishLoading The function to call when loading is complete
@ -355,22 +438,32 @@ export default class ResourceManager {
// We can parse the object later - it's much faster than loading // We can parse the object later - it's much faster than loading
this.tilemaps.add(key, tilemapObject); this.tilemaps.add(key, tilemapObject);
let resource = new ResourceReference(key, ResourceType.TILEMAP);
// Grab the tileset images we need to load and add them to the imageloading queue // Grab the tileset images we need to load and add them to the imageloading queue
for(let tileset of tilemapObject.tilesets){ for(let tileset of tilemapObject.tilesets){
if(tileset.image){ if(tileset.image){
let key = tileset.image; let key = tileset.image;
let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key; let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key;
this.loadonly_imageLoadingQueue.enqueue({key: key, path: path}); this.loadonly_imageLoadingQueue.enqueue({key: key, path: path, isDependency: true});
// Add this image as a dependency to the tilemap
resource.addDependency(new ResourceReference(key, ResourceType.IMAGE));
} else if(tileset.tiles){ } else if(tileset.tiles){
for(let tile of tileset.tiles){ for(let tile of tileset.tiles){
let key = tile.image; let key = tile.image;
let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key; let path = StringUtils.getPathFromFilePath(pathToTilemapJSON) + key;
this.loadonly_imageLoadingQueue.enqueue({key: key, path: path}); this.loadonly_imageLoadingQueue.enqueue({key: key, path: path, isDependency: true});
// Add this image as a dependency to the tilemap
resource.addDependency(new ResourceReference(key, ResourceType.IMAGE));
} }
} }
} }
// Add the resource reference to the list of resource to unload
this.resourcesToUnload.push(resource);
// Finish loading // Finish loading
this.finishLoadingTilemap(callbackIfLast); this.finishLoadingTilemap(callbackIfLast);
}); });
@ -422,9 +515,14 @@ export default class ResourceManager {
// We can parse the object later - it's much faster than loading // We can parse the object later - it's much faster than loading
this.spritesheets.add(key, spritesheet); this.spritesheets.add(key, spritesheet);
let resource = new ResourceReference(key, ResourceType.SPRITESHEET);
// Grab the image we need to load and add it to the imageloading queue // Grab the image we need to load and add it to the imageloading queue
let path = StringUtils.getPathFromFilePath(pathToSpritesheetJSON) + spritesheet.spriteSheetImage; let path = StringUtils.getPathFromFilePath(pathToSpritesheetJSON) + spritesheet.spriteSheetImage;
this.loadonly_imageLoadingQueue.enqueue({key: spritesheet.name, path: path}); this.loadonly_imageLoadingQueue.enqueue({key: spritesheet.name, path: path, isDependency: true});
resource.addDependency(new ResourceReference(spritesheet.name, ResourceType.IMAGE));
this.resourcesToUnload.push(resource);
// Finish loading // Finish loading
this.finishLoadingSpritesheet(callbackIfLast); this.finishLoadingSpritesheet(callbackIfLast);
@ -460,7 +558,7 @@ export default class ResourceManager {
while(this.loadonly_imageLoadingQueue.hasItems()){ while(this.loadonly_imageLoadingQueue.hasItems()){
let image = this.loadonly_imageLoadingQueue.dequeue(); let image = this.loadonly_imageLoadingQueue.dequeue();
this.loadImage(image.key, image.path, onFinishLoading); this.loadImage(image.key, image.path, image.isDependency, onFinishLoading);
} }
} }
@ -470,13 +568,18 @@ export default class ResourceManager {
* @param path The path to the image to load * @param path The path to the image to load
* @param callbackIfLast The function to call if this is the last image * @param callbackIfLast The function to call if this is the last image
*/ */
public loadImage(key: string, path: string, callbackIfLast: Function): void { public loadImage(key: string, path: string, isDependency: boolean, callbackIfLast: Function): void {
var image = new Image(); var image = new Image();
image.onload = () => { image.onload = () => {
// Add to loaded images // Add to loaded images
this.images.add(key, image); this.images.add(key, image);
// If not a dependency, push it to the unload list. Otherwise it's managed by something else
if(!isDependency){
this.resourcesToUnload.push(new ResourceReference(key, ResourceType.IMAGE));
}
// If WebGL is active, create a texture // If WebGL is active, create a texture
if(this.gl_WebGLActive){ if(this.gl_WebGLActive){
this.createWebGLTexture(key, image); this.createWebGLTexture(key, image);
@ -539,6 +642,7 @@ export default class ResourceManager {
audioCtx.decodeAudioData(request.response, (buffer) => { audioCtx.decodeAudioData(request.response, (buffer) => {
// Add to list of audio buffers // Add to list of audio buffers
this.audioBuffers.add(key, buffer); this.audioBuffers.add(key, buffer);
this.resourcesToUnload.push(new ResourceReference(key, ResourceType.AUDIO));
// Finish loading sound // Finish loading sound
this.finishLoadingAudio(callbackIfLast); this.finishLoadingAudio(callbackIfLast);
@ -592,6 +696,9 @@ export default class ResourceManager {
this.loadTextFile(path, (fileText: string) => { this.loadTextFile(path, (fileText: string) => {
let obj = JSON.parse(fileText); let obj = JSON.parse(fileText);
this.jsonObjects.add(key, obj); this.jsonObjects.add(key, obj);
this.resourcesToUnload.push(new ResourceReference(key, ResourceType.JSON));
this.finishLoadingObject(callbackIfLast); this.finishLoadingObject(callbackIfLast);
}); });
} }
@ -706,6 +813,14 @@ export default class ResourceManager {
this.loadonly_gl_ShaderLoadingQueue.enqueue(paths); this.loadonly_gl_ShaderLoadingQueue.enqueue(paths);
} }
/**
* Tells the resource manager to keep this resource
* @param key The key of the resource
*/
public keepShader(key: string): void {
this.keepResource(key, ResourceType.IMAGE);
}
private gl_LoadShadersFromQueue(onFinishLoading: Function): void { private gl_LoadShadersFromQueue(onFinishLoading: Function): void {
this.loadonly_gl_ShaderProgramsToLoad = this.loadonly_gl_ShaderLoadingQueue.getSize(); this.loadonly_gl_ShaderProgramsToLoad = this.loadonly_gl_ShaderLoadingQueue.getSize();
this.loadonly_gl_ShaderProgramsLoaded = 0; this.loadonly_gl_ShaderProgramsLoaded = 0;
@ -741,6 +856,8 @@ export default class ResourceManager {
// Add to our map // Add to our map
this.gl_ShaderPrograms.add(key, programWrapper); this.gl_ShaderPrograms.add(key, programWrapper);
this.resourcesToUnload.push(new ResourceReference(key, ResourceType.SHADER));
// Finish loading // Finish loading
this.gl_FinishLoadingShader(callbackIfLast); this.gl_FinishLoadingShader(callbackIfLast);
}); });
@ -769,7 +886,7 @@ export default class ResourceManager {
const program = this.gl.createProgram(); const program = this.gl.createProgram();
if(!program) { if(!program) {
// Error creating // Error creating
console.log("Failed to create program"); console.warn("Failed to create program");
return null; return null;
} }
@ -782,7 +899,7 @@ export default class ResourceManager {
if(!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)){ if(!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)){
// Error linking // Error linking
const error = this.gl.getProgramInfoLog(program); const error = this.gl.getProgramInfoLog(program);
console.log("Failed to link program: " + error); console.warn("Failed to link program: " + error);
// Clean up // Clean up
this.gl.deleteProgram(program); this.gl.deleteProgram(program);
@ -810,7 +927,7 @@ export default class ResourceManager {
// If we couldn't create the shader, error // If we couldn't create the shader, error
if(shader === null){ if(shader === null){
console.log("Unable to create shader"); console.warn("Unable to create shader");
return null; return null;
} }
@ -822,7 +939,7 @@ export default class ResourceManager {
if(!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)){ if(!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)){
// Not compiled - error // Not compiled - error
const error = this.gl.getShaderInfoLog(shader); const error = this.gl.getShaderInfoLog(shader);
console.log("Failed to compile shader: " + error); console.warn("Failed to compile shader: " + error);
// Clean up // Clean up
this.gl.deleteShader(shader); this.gl.deleteShader(shader);
@ -871,9 +988,44 @@ export default class ResourceManager {
} }
} }
/**
* A class representing a reference to a resource.
* This is used for the exemption list to assure assets and their dependencies don't get
* destroyed if they are still needed.
*/
class ResourceReference {
key: string;
resourceType: ResourceType;
dependencies: Array<ResourceReference>;
constructor(key: string, resourceType: ResourceType){
this.key = key;
this.resourceType = resourceType;
this. dependencies = new Array();
}
addDependency(resource: ResourceReference): void {
this.dependencies.push(resource);
}
}
enum ResourceType {
IMAGE = "IMAGE",
TILEMAP = "TILEMAP",
SPRITESHEET = "SPRITESHEET",
AUDIO = "AUDIO",
JSON = "JSON",
SHADER = "SHADER"
}
/**
* A pair representing a key and the path of the resource to load
*/
class KeyPathPair { class KeyPathPair {
key: string; key: string;
path: string; path: string;
isDependency?: boolean = false;
} }
class KeyPath_Shader { class KeyPath_Shader {

View File

@ -90,7 +90,6 @@ export default class TilemapFactory {
} }
if(isParallaxLayer){ if(isParallaxLayer){
console.log("Adding parallax layer: " + layer.name)
sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1), depth); sceneLayer = this.scene.addParallaxLayer(layer.name, new Vec2(1, 1), depth);
} else { } else {
sceneLayer = this.scene.addLayer(layer.name, depth); sceneLayer = this.scene.addLayer(layer.name, depth);

View File

@ -81,9 +81,12 @@ export default class Scene implements Updateable {
/** An interface that allows the adding of different nodes to the scene */ /** An interface that allows the adding of different nodes to the scene */
public add: FactoryManager; public add: FactoryManager;
/** An interface that allows the loading of different files for use in the scene */ /** An interface that allows the loading of different files for use in the scene. An alias for resourceManager */
public load: ResourceManager; public load: ResourceManager;
/** An interface that allows the loading and unloading of different files for use in the scene */
public resourceManager: ResourceManager;
/** The configuration options for this scene */ /** The configuration options for this scene */
public sceneOptions: SceneOptions; public sceneOptions: SceneOptions;
@ -120,7 +123,8 @@ export default class Scene implements Updateable {
this.add = new FactoryManager(this, this.tilemaps); this.add = new FactoryManager(this, this.tilemaps);
this.load = ResourceManager.getInstance(); this.load = ResourceManager.getInstance()
this.resourceManager = this.load;
// Get the timer manager and clear any existing timers // Get the timer manager and clear any existing timers
TimerManager.getInstance().clearTimers(); TimerManager.getInstance().clearTimers();
@ -132,9 +136,6 @@ export default class Scene implements Updateable {
/** A lifecycle method that gets called when a new scene is created. Load all files you wish to access in the scene here. */ /** A lifecycle method that gets called when a new scene is created. Load all files you wish to access in the scene here. */
loadScene(): void {} loadScene(): void {}
/** A lifecycle method that gets called on scene destruction. Specify which files you no longer need for garbage collection. */
unloadScene(): void {}
/** A lifecycle method called strictly after loadScene(). Create any game objects you wish to use in the scene here. */ /** A lifecycle method called strictly after loadScene(). Create any game objects you wish to use in the scene here. */
startScene(): void {} startScene(): void {}
@ -144,6 +145,9 @@ export default class Scene implements Updateable {
*/ */
updateScene(deltaT: number): void {} updateScene(deltaT: number): void {}
/** A lifecycle method that gets called on scene destruction. Specify which files you no longer need for garbage collection. */
unloadScene(): void {}
update(deltaT: number): void { update(deltaT: number): void {
this.updateScene(deltaT); this.updateScene(deltaT);
@ -232,6 +236,12 @@ export default class Scene implements Updateable {
node.destroy(); node.destroy();
} }
for(let tilemap of this.tilemaps){
tilemap.destroy();
}
this.receiver.destroy();
delete this.sceneGraph; delete this.sceneGraph;
delete this.physicsManager; delete this.physicsManager;
delete this.navManager; delete this.navManager;

View File

@ -2,6 +2,7 @@ import Scene from "./Scene";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import RenderingManager from "../Rendering/RenderingManager"; import RenderingManager from "../Rendering/RenderingManager";
import MemoryUtils from "../Utils/MemoryUtils";
/** /**
* The SceneManager acts as an interface to create Scenes, and handles the lifecycle methods of Scenes. * The SceneManager acts as an interface to create Scenes, and handles the lifecycle methods of Scenes.
@ -25,6 +26,7 @@ export default class SceneManager {
/** For consistency, only change scenes at the beginning of the update cycle */ /** For consistency, only change scenes at the beginning of the update cycle */
protected pendingScene: Scene; protected pendingScene: Scene;
protected pendingSceneInit: Record<string, any>;
/** /**
* Creates a new SceneManager * Creates a new SceneManager
@ -37,6 +39,7 @@ export default class SceneManager {
this.viewport = viewport; this.viewport = viewport;
this.renderingManager = renderingManager; this.renderingManager = renderingManager;
this.idCounter = 0; this.idCounter = 0;
this.pendingScene = null;
} }
/** /**
@ -46,31 +49,47 @@ export default class SceneManager {
* @param init An object to pass to the init function of the new scene * @param init An object to pass to the init function of the new scene
*/ */
public changeToScene<T extends Scene>(constr: new (...args: any) => T, init?: Record<string, any>, options?: Record<string, any>): void { public changeToScene<T extends Scene>(constr: new (...args: any) => T, init?: Record<string, any>, options?: Record<string, any>): void {
console.log("Creating the new scene - change is pending until next update");
this.pendingScene = new constr(this.viewport, this, this.renderingManager, options);
this.pendingSceneInit = init;
}
protected doSceneChange(){
console.log("Performing scene change");
this.viewport.setCenter(this.viewport.getHalfSize().x, this.viewport.getHalfSize().y); this.viewport.setCenter(this.viewport.getHalfSize().x, this.viewport.getHalfSize().y);
let scene = new constr(this.viewport, this, this.renderingManager, options);
if(this.currentScene){ if(this.currentScene){
console.log("Destroying Old Scene"); console.log("Unloading old scene")
this.currentScene.unloadScene();
console.log("Destroying old scene");
this.currentScene.destroy(); this.currentScene.destroy();
} }
this.currentScene = scene; console.log("Unloading old resources...");
this.resourceManager.unloadAllResources();
scene.initScene(init); // Make the pending scene the current one
this.currentScene = this.pendingScene;
// Make the pending scene null
this.pendingScene = null;
// Init the scene
this.currentScene.initScene(this.pendingSceneInit);
// Enqueue all scene asset loads // Enqueue all scene asset loads
scene.loadScene(); this.currentScene.loadScene();
// Load all assets // Load all assets
console.log("Starting Scene Load"); console.log("Starting Scene Load");
this.resourceManager.loadResourcesFromQueue(() => { this.resourceManager.loadResourcesFromQueue(() => {
console.log("Starting Scene"); console.log("Starting Scene");
scene.startScene(); this.currentScene.startScene();
scene.setRunning(true); this.currentScene.setRunning(true);
}); });
this.renderingManager.setScene(scene); this.renderingManager.setScene(this.currentScene);
} }
/** /**
@ -85,15 +104,21 @@ export default class SceneManager {
* Renders the current Scene * Renders the current Scene
*/ */
public render(): void { public render(): void {
if(this.currentScene){
this.currentScene.render(); this.currentScene.render();
} }
}
/** /**
* Updates the current Scene * Updates the current Scene
* @param deltaT The timestep of the Scene * @param deltaT The timestep of the Scene
*/ */
public update(deltaT: number){ public update(deltaT: number){
if(this.currentScene.isRunning()){ if(this.pendingScene !== null){
this.doSceneChange();
}
if(this.currentScene && this.currentScene.isRunning()){
this.currentScene.update(deltaT); this.currentScene.update(deltaT);
} }
} }

View File

@ -155,7 +155,7 @@ export default class Viewport {
* @param zoom The zoom level * @param zoom The zoom level
*/ */
setZoomLevel(zoom: number): void { setZoomLevel(zoom: number): void {
this.view.halfSize.scale(1/zoom); this.view.halfSize.copy(this.canvasSize.scaled(1/zoom/2));
} }
/** /**

View File

@ -46,7 +46,7 @@ export default class AudioManager {
this.audioCtx = new AudioContext(); this.audioCtx = new AudioContext();
console.log('Web Audio API successfully loaded'); console.log('Web Audio API successfully loaded');
} catch(e) { } catch(e) {
console.log('Web Audio API is not supported in this browser'); console.warn('Web Audio API is not supported in this browser');
} }
} }

View File

@ -0,0 +1,46 @@
import Stack from "../DataTypes/Stack";
export default class MemoryUtils {
/**
* Returns an approximate size in bytes of any object
* @param obj The object to get the size of
* @returns An approximation of the number of bytes in the object provided
*/
static approxSizeOf(obj: any): number {
let objectList = new Array<object>();
let stack = new Stack<any>(10000);
stack.push(obj);
let bytes = 0;
while(!stack.isEmpty()){
let item = stack.pop();
// We aren't interested in counting window and document
if(item === window || item === document) continue;
if((typeof item) === "boolean"){
bytes += 4;
} else if((typeof item) === "number"){
bytes += 8;
} else if((typeof item) === "string"){
bytes += item.length;
} else if((typeof item) === "object" && objectList.indexOf(item) === -1){
// We haven't seen this object before - log it
objectList.push(item);
// Get the number of bytes in all of its fields
for(let field in item){
try {
stack.push(item[field]);
} catch(e){
console.log("Pushing item: ", + item[field]);
console.warn(`Ran out of room counting memory (Stack has size ${stack.size()}) - returning current number of bytes`);
return bytes;
}
}
}
}
return bytes;
}
}