@bscotch/sprite-source
Version:
Art pipeline scripting module for GameMaker sprites.
153 lines • 6.28 kB
JavaScript
import { __decorate, __metadata } from "tslib";
import { existsSafe, pathy } from '@bscotch/pathy';
import { sequential } from '@bscotch/utility';
import { SpriteCache } from './SpriteCache.js';
import { spriteSourceConfigSchema, } from './SpriteSource.schemas.js';
import { FIO_RETRY_DELAY, MAX_FIO_RETRIES, retryOptions } from './constants.js';
import { SpriteSourceError, assert, deletePngChildren, getDirs, rethrow, } from './utility.js';
export class SpriteSource extends SpriteCache {
get configFile() {
return this.stitchDir
.join('sprites.source.json')
.withValidator(spriteSourceConfigSchema);
}
async resolveStaged(staging) {
const dir = pathy(staging.dir, this.spritesRoot);
if (!(await existsSafe(dir, retryOptions))) {
this.issues.push(new SpriteSourceError(`Staging directory does not exist: ${dir}`));
return;
}
// Identify all "SpriteDirs". Stored as a set so we
// can remove the ones we process.
const spriteDirs = new Set(await this.getSpriteDirs(await getDirs(dir.absolute)));
for (const transform of staging.transforms) {
// filter to matching sprites
const pattern = transform.include
? new RegExp(transform.include)
: undefined;
const sprites = [];
for (const sprite of spriteDirs) {
if (!pattern || sprite.path.relative.match(pattern)) {
sprites.push(sprite);
spriteDirs.delete(sprite);
}
}
const waits = [];
for (const sprite of sprites) {
let outDirPath = sprite.path.relative;
if (transform.renames) {
for (const rename of transform.renames) {
outDirPath = outDirPath.replace(new RegExp(rename.from), rename.to);
}
}
const outDir = pathy(outDirPath, this.spritesRoot);
// Crop it!
const cropWait = transform.crop ? sprite.crop() : Promise.resolve();
// Bleed it!
const bleedWait = cropWait.then(() => transform.bleed ? sprite.bleed() : Promise.resolve());
// Purge the output location!
const deleteWait = bleedWait.then(() => transform.synced
? deletePngChildren(outDir).then((deleted) => {
this.logs.push(...deleted.map((path) => ({
action: 'deleted',
path: path.absolute,
})));
})
: Promise.resolve());
// Save to destination (including renames)
const moveWait = deleteWait.then(() => sprite.moveTo(outDir));
waits.push(moveWait);
}
await Promise.allSettled(waits);
// Delete any folders-of-empty-folders
const dirs = await getDirs(dir.absolute);
for (const dir of dirs) {
if ((await dir.exists()) && (await dir.isEmptyDirectory())) {
await dir.delete({
recursive: true,
force: true,
retryDelay: FIO_RETRY_DELAY,
maxRetries: MAX_FIO_RETRIES,
});
}
}
}
}
async loadConfig(overrides = {}) {
// Validate options. Show error out if invalid.
try {
overrides = spriteSourceConfigSchema.parse(overrides);
}
catch (err) {
rethrow(err, 'Invalid SpriteSource options');
}
assert(await this.spritesRoot.isDirectory(), 'Source must be an existing directory.');
// Update the config
await this.stitchDir.ensureDirectory();
const config = await this.configFile.read({
fallback: {},
maxRetries: MAX_FIO_RETRIES,
retryDelayMillis: FIO_RETRY_DELAY,
});
const snapshot = JSON.stringify(config);
if (overrides?.ignore !== undefined) {
config.ignore = overrides.ignore;
}
if (overrides?.staging !== undefined) {
config.staging = overrides.staging;
}
if (snapshot !== JSON.stringify(config)) {
try {
await this.configFile.write(config, {
maxRetries: MAX_FIO_RETRIES,
retryDelayMillis: FIO_RETRY_DELAY,
});
}
catch (err) {
console.error(err);
}
}
return config;
}
/**
* Transform any staged sprites and add them to the source,
* and compute updated sprite info.
*/
async update(
/** Optionally override config options */
options) {
const config = await this.loadConfig(options);
// Process any staging folders
for (const staging of config.staging ?? []) {
// Do them sequentially since later patterns could overlap earlier ones
await this.resolveStaged(staging);
}
// Update the sprite info cache
const info = await this.updateSpriteInfo(config.ignore);
return info;
}
/**
* @param options If specified, creates/overwrites the config file with these options.
*/
static async from(spritesRoot, options) {
// Ensure the spritesRoot exists
assert(await existsSafe(spritesRoot, retryOptions), `Sprites root does not exist: ${spritesRoot}`);
const source = new SpriteSource(spritesRoot);
// Ensure a config file
await source.loadConfig(options);
return source;
}
}
__decorate([
sequential,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], SpriteSource.prototype, "loadConfig", null);
__decorate([
sequential,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], SpriteSource.prototype, "update", null);
//# sourceMappingURL=SpriteSource.js.map