nitro-renderer/src/nitro/avatar/AvatarAssetDownloadManager.ts

344 lines
9.9 KiB
TypeScript

import { IAssetManager } from '../../core/asset/IAssetManager';
import { NitroLogger } from '../../core/common/logger/NitroLogger';
import { EventDispatcher } from '../../core/events/EventDispatcher';
import { NitroEvent } from '../../core/events/NitroEvent';
import { Nitro } from '../Nitro';
import { AvatarAssetDownloadLibrary } from './AvatarAssetDownloadLibrary';
import { AvatarStructure } from './AvatarStructure';
import { AvatarRenderEvent } from './events/AvatarRenderEvent';
import { AvatarRenderLibraryEvent } from './events/AvatarRenderLibraryEvent';
import { IAvatarFigureContainer } from './IAvatarFigureContainer';
import { IAvatarImageListener } from './IAvatarImageListener';
export class AvatarAssetDownloadManager extends EventDispatcher
{
public static DOWNLOADER_READY: string = 'AADM_DOWNLOADER_READY';
public static LIBRARY_LOADED: string = 'AADM_LIBRARY_LOADED';
private static MAX_DOWNLOADS: number = 2;
private _assets: IAssetManager;
private _structure: AvatarStructure;
private _missingMandatoryLibs: string[];
private _figureMap: Map<string, AvatarAssetDownloadLibrary[]>;
private _pendingContainers: [ IAvatarFigureContainer, IAvatarImageListener ][];
private _figureListeners: Map<string, IAvatarImageListener[]>;
private _incompleteFigures: Map<string, AvatarAssetDownloadLibrary[]>;
private _pendingDownloadQueue: AvatarAssetDownloadLibrary[];
private _currentDownloads: AvatarAssetDownloadLibrary[];
private _libraryNames: string[];
private _isReady: boolean;
constructor(assets: IAssetManager, structure: AvatarStructure)
{
super();
this._assets = assets;
this._structure = structure;
this._missingMandatoryLibs = Nitro.instance.getConfiguration<string[]>('avatar.mandatory.libraries');
this._figureMap = new Map();
this._pendingContainers = [];
this._figureListeners = new Map();
this._incompleteFigures = new Map();
this._pendingDownloadQueue = [];
this._currentDownloads = [];
this._libraryNames = [];
this._isReady = false;
this.onLibraryLoaded = this.onLibraryLoaded.bind(this);
this.onAvatarRenderReady = this.onAvatarRenderReady.bind(this);
this.loadFigureMap();
this._structure.renderManager.events.addEventListener(AvatarRenderEvent.AVATAR_RENDER_READY, this.onAvatarRenderReady);
}
private loadFigureMap(): void
{
const request = new XMLHttpRequest();
try
{
request.open('GET', Nitro.instance.getConfiguration<string>('avatar.figuremap.url'));
request.send();
request.onloadend = e =>
{
if(request.responseText)
{
const data = JSON.parse(request.responseText);
this.processFigureMap(data.libraries);
this.processMissingLibraries();
this._isReady = true;
this.dispatchEvent(new NitroEvent(AvatarAssetDownloadManager.DOWNLOADER_READY));
}
};
request.onerror = e =>
{
throw new Error('invalid_avatar_figure_map');
};
}
catch (e)
{
NitroLogger.log(e);
}
}
private processFigureMap(data: any): void
{
if(!data) return;
for(const library of data)
{
if(!library) continue;
const id = (library.id as string);
const revision = (library.revision || '');
if(this._libraryNames.indexOf(id) >= 0) continue;
this._libraryNames.push(id);
const downloadLibrary = new AvatarAssetDownloadLibrary(id, revision, this._assets, Nitro.instance.getConfiguration<string>('avatar.asset.url'));
downloadLibrary.addEventListener(AvatarRenderLibraryEvent.DOWNLOAD_COMPLETE, this.onLibraryLoaded);
for(const part of library.parts)
{
const id = (part.id as string);
const type = (part.type as string);
const partString = (type + ':' + id);
let existing = this._figureMap.get(partString);
if(!existing) existing = [];
existing.push(downloadLibrary);
this._figureMap.set(partString, existing);
}
}
}
private onAvatarRenderReady(event: NitroEvent): void
{
if(!event) return;
for(const [ container, listener ] of this._pendingContainers)
{
this.downloadAvatarFigure(container, listener);
}
this._pendingContainers = [];
}
private onLibraryLoaded(event: AvatarRenderLibraryEvent): void
{
if(!event || !event.library) return;
const loadedFigures: string[] = [];
for(const [ figure, libraries ] of this._incompleteFigures.entries())
{
let isReady = true;
for(const library of libraries)
{
if(!library || library.isLoaded) continue;
isReady = false;
break;
}
if(isReady)
{
loadedFigures.push(figure);
const listeners = this._figureListeners.get(figure);
if(listeners)
{
for(const listener of listeners)
{
if(!listener || listener.disposed) continue;
listener.resetFigure(figure);
}
}
this._figureListeners.delete(figure);
this.dispatchEvent(new NitroEvent(AvatarAssetDownloadManager.LIBRARY_LOADED));
}
}
for(const figure of loadedFigures)
{
if(!figure) continue;
this._incompleteFigures.delete(figure);
}
let index = 0;
while(index < this._currentDownloads.length)
{
const download = this._currentDownloads[index];
if(download)
{
if(download.libraryName === event.library.libraryName) this._currentDownloads.splice(index, 1);
}
index++;
}
}
public processMissingLibraries(): void
{
const libraries = this._missingMandatoryLibs.slice();
for(const library of libraries)
{
if(!library) continue;
const map = this._figureMap.get(library);
if(map) for(const avatar of map) avatar && this.downloadLibrary(avatar);
}
}
public isAvatarFigureContainerReady(container: IAvatarFigureContainer): boolean
{
if(!this._isReady || !this._structure.renderManager.isReady)
{
return false;
}
const pendingLibraries = this.getAvatarFigurePendingLibraries(container);
return !pendingLibraries.length;
}
private getAvatarFigurePendingLibraries(container: IAvatarFigureContainer): AvatarAssetDownloadLibrary[]
{
const pendingLibraries: AvatarAssetDownloadLibrary[] = [];
if(!container || !this._structure) return pendingLibraries;
const figureData = this._structure.figureData;
if(!figureData) return pendingLibraries;
const setKeys = container.getPartTypeIds();
for(const key of setKeys)
{
const set = figureData.getSetType(key);
if(!set) continue;
const figurePartSet = set.getPartSet(container.getPartSetId(key));
if(!figurePartSet) continue;
for(const part of figurePartSet.parts)
{
if(!part) continue;
const name = (part.type + ':' + part.id);
const existing = this._figureMap.get(name);
if(existing === undefined) continue;
for(const library of existing)
{
if(!library || library.isLoaded) continue;
if(pendingLibraries.indexOf(library) >= 0) continue;
pendingLibraries.push(library);
}
}
}
return pendingLibraries;
}
public downloadAvatarFigure(container: IAvatarFigureContainer, listener: IAvatarImageListener): void
{
if(!this._isReady || !this._structure.renderManager.isReady)
{
this._pendingContainers.push([ container, listener ]);
return;
}
const figure = container.getFigureString();
const pendingLibraries = this.getAvatarFigurePendingLibraries(container);
if(pendingLibraries && pendingLibraries.length)
{
if(listener && !listener.disposed)
{
let listeners = this._figureListeners.get(figure);
if(!listeners)
{
listeners = [];
this._figureListeners.set(figure, listeners);
}
listeners.push(listener);
}
this._incompleteFigures.set(figure, pendingLibraries);
for(const library of pendingLibraries)
{
if(!library) continue;
this.downloadLibrary(library);
}
}
else
{
if(listener && !listener.disposed) listener.resetFigure(figure);
}
}
private downloadLibrary(library: AvatarAssetDownloadLibrary): void
{
if(!library || library.isLoaded) return;
if((this._pendingDownloadQueue.indexOf(library) >= 0) || (this._currentDownloads.indexOf(library) >= 0)) return;
this._pendingDownloadQueue.push(library);
this.processDownloadQueue();
}
private processDownloadQueue(): void
{
while(this._pendingDownloadQueue.length)
{
const library = this._pendingDownloadQueue[0];
library.downloadAsset();
this._currentDownloads.push(this._pendingDownloadQueue.shift());
}
}
}