<commit_before>import { Rectangle } from './coords';

const spriteSets = [null, 'mansion', 'forest', 'city', 'laboratory', 'tutorial', 'nexus'];
const propGroups = [
    'books', 'buildingblocks', 'chains', 'decoration', 'facade', 'foliage', 'furniture', 'gazebo',
    'lighting', null, 'statues', 'storage', 'study', 'fencing', null, null,
    null, null, 'backleaves', 'leaves', 'trunks', 'boulders', 'backdrops', 'temple',
    'npc', 'symbol', 'cars', 'sidewalk', 'machinery'
];

const IDLE = 0;
const LOADING = 1;
const LOADED = 2;

export class FrameContainer {
    public state: number;
    private imageLoaded: boolean;
    private metadataXHR: XMLHttpRequest;
    private metadataLoaded: boolean;
    private metadata: FrameMetadata;
    public texture: PIXI.Texture;
    private image: HTMLImageElement;
    public onloaded: () => void;
    public frame: Frame;

    constructor(private imageURL: string, private metadataURL: string, public priority: number) {
        this.state = IDLE;
        this.image = document.createElement('img');
        this.texture = new PIXI.Texture(new PIXI.BaseTexture(this.image));
    }

    public load() {
        this.state = LOADING;

        this.image.src = this.imageURL;
        this.texture.baseTexture.on('loaded', () => { this.onImageLoaded(); });
        this.texture.baseTexture.on('error', () => { this.onImageLoaded(); });

        if (this.metadataURL) {
            this.metadataXHR = new XMLHttpRequest();
            this.metadataXHR.onload = () => { this.onMetadataLoaded(); };
            this.metadataXHR.onerror = () => { this.onMetadataLoaded(); };
            this.metadataXHR.open('get', this.metadataURL);
            this.metadataXHR.send();
        }
    }

    private onImageLoaded() {
        this.imageLoaded = true;
        this.checkFullyLoaded();
    }

    private onMetadataLoaded() {
        if (this.metadataXHR.status === 200)
            this.metadata = JSON.parse(this.metadataXHR.response);
        this.metadataLoaded = true;
        this.checkFullyLoaded();
    }

    private checkFullyLoaded() {
        if (!this.imageLoaded || (this.metadataURL && !this.metadataLoaded))
            return;

        var hitbox: Rectangle;
        if (this.metadata) {
            hitbox = Rectangle.ltrb(this.metadata.hitbox[1], this.metadata.hitbox[0],
                                    this.metadata.hitbox[3], this.metadata.hitbox[2]);
        } else if (this.image.complete) {
            hitbox = new Rectangle(0, 0, this.image.naturalWidth, this.image.naturalHeight);
        }

        if (hitbox)
            this.frame = new Frame(this.texture, hitbox);

        this.state = LOADED;
        this.onloaded && this.onloaded();
    }
}

class FrameManager {
    private all: { [url: string]: FrameContainer } = {};
    private loading = 0;
    private unloaded: FrameContainer[] = [];

    constructor() { }

    public getFrame(imageURL: string, metadataURL: string, priority: number) {
        var fc = this.all[imageURL];
        if (fc)
            return fc;

        fc = new FrameContainer(imageURL, metadataURL, priority);
        this.all[imageURL] = fc;
        var uidx = _.sortedIndex(this.unloaded, fc, fc => fc.priority);
        this.unloaded.splice(uidx, 0, fc);
        this.fillLoadQueue();
        return fc;
    }

    private fillLoadQueue() {
        while (this.loading < 4 && this.unloaded.length) {
            var fc = this.unloaded.pop();
            fc.load();
            ++this.loading;
            fc.onloaded = () => { --this.loading; this.fillLoadQueue(); };
        }
    }
}

var frameManager = new FrameManager();

export function getFrame(name: string, priority: number) {
    return frameManager.getFrame(frameImageURL(name), frameMetadataURL(name), priority);
}

export function getFrameFromRawImage(imageURL: string, priority: number) {
    return frameManager.getFrame(imageURL, null, priority);
}

export function frameImageURL(name: string) {
    return '/assets/sprites/' + name + '.png';
}

export function frameMetadataURL(name: string) {
    return '/assets/sprites/' + name + '.json';
}

export class Frame {
    constructor(public texture: PIXI.Texture, public hitbox: Rectangle) { }
}<commit_msg>Increase client download parallelism<commit_after>import { Rectangle } from './coords';

const spriteSets = [null, 'mansion', 'forest', 'city', 'laboratory', 'tutorial', 'nexus'];
const propGroups = [
    'books', 'buildingblocks', 'chains', 'decoration', 'facade', 'foliage', 'furniture', 'gazebo',
    'lighting', null, 'statues', 'storage', 'study', 'fencing', null, null,
    null, null, 'backleaves', 'leaves', 'trunks', 'boulders', 'backdrops', 'temple',
    'npc', 'symbol', 'cars', 'sidewalk', 'machinery'
];

const IDLE = 0;
const LOADING = 1;
const LOADED = 2;

export class FrameContainer {
    public state: number;
    private imageLoaded: boolean;
    private metadataXHR: XMLHttpRequest;
    private metadataLoaded: boolean;
    private metadata: FrameMetadata;
    public texture: PIXI.Texture;
    private image: HTMLImageElement;
    public onloaded: () => void;
    public frame: Frame;

    constructor(private imageURL: string, public metadataURL: string, public priority: number) {
        this.state = IDLE;
        this.image = document.createElement('img');
        this.texture = new PIXI.Texture(new PIXI.BaseTexture(this.image));
    }

    public load() {
        this.state = LOADING;

        this.image.src = this.imageURL;
        this.texture.baseTexture.on('loaded', () => { this.onImageLoaded(); });
        this.texture.baseTexture.on('error', () => { this.onImageLoaded(); });

        if (this.metadataURL) {
            this.metadataXHR = new XMLHttpRequest();
            this.metadataXHR.onload = () => { this.onMetadataLoaded(); };
            this.metadataXHR.onerror = () => { this.onMetadataLoaded(); };
            this.metadataXHR.open('get', this.metadataURL);
            this.metadataXHR.send();
        }
    }

    private onImageLoaded() {
        this.imageLoaded = true;
        this.checkFullyLoaded();
    }

    private onMetadataLoaded() {
        if (this.metadataXHR.status === 200)
            this.metadata = JSON.parse(this.metadataXHR.response);
        this.metadataLoaded = true;
        this.checkFullyLoaded();
    }

    private checkFullyLoaded() {
        if (!this.imageLoaded || (this.metadataURL && !this.metadataLoaded))
            return;

        var hitbox: Rectangle;
        if (this.metadata) {
            hitbox = Rectangle.ltrb(this.metadata.hitbox[1], this.metadata.hitbox[0],
                                    this.metadata.hitbox[3], this.metadata.hitbox[2]);
        } else if (this.image.complete) {
            hitbox = new Rectangle(0, 0, this.image.naturalWidth, this.image.naturalHeight);
        }

        if (hitbox)
            this.frame = new Frame(this.texture, hitbox);

        this.state = LOADED;
        this.onloaded && this.onloaded();
    }
}

class FrameManager {
    private all: { [url: string]: FrameContainer } = {};
    private loading = 0;
    private unloaded: FrameContainer[] = [];

    constructor() { }

    public getFrame(imageURL: string, metadataURL: string, priority: number) {
        var fc = this.all[imageURL];
        if (fc)
            return fc;

        fc = new FrameContainer(imageURL, metadataURL, priority);
        this.all[imageURL] = fc;
        var uidx = _.sortedIndex(this.unloaded, fc, fc => fc.priority);
        this.unloaded.splice(uidx, 0, fc);
        this.fillLoadQueue();
        return fc;
    }

    private fillLoadQueue() {
        while (this.loading < 8 && this.unloaded.length) {
            var fc = this.unloaded.pop();
            var fcNumDownloads = 1 + (fc.metadataURL ? 1 : 0);

            fc.load();
            this.loading += fcNumDownloads;

            fc.onloaded = () => {
                this.loading -= fcNumDownloads;
                this.fillLoadQueue();
            };
        }
    }
}

var frameManager = new FrameManager();

export function getFrame(name: string, priority: number) {
    return frameManager.getFrame(frameImageURL(name), frameMetadataURL(name), priority);
}

export function getFrameFromRawImage(imageURL: string, priority: number) {
    return frameManager.getFrame(imageURL, null, priority);
}

export function frameImageURL(name: string) {
    return '/assets/sprites/' + name + '.png';
}

export function frameMetadataURL(name: string) {
    return '/assets/sprites/' + name + '.json';
}

export class Frame {
    constructor(public texture: PIXI.Texture, public hitbox: Rectangle) { }
}
{'fraction_non_alphanumeric': 0.13886634983192303, 'fraction_numerical': 0.00382519995363394, 'mean_word_length': 2.6840307429547394, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2083291', 'n_tokens_mistral': 2637, 'n_tokens_neox': 2555, 'n_words': 577}