diff --git a/out/AssetConverter.js b/out/AssetConverter.js index 18ce4b60..a815c916 100644 --- a/out/AssetConverter.js +++ b/out/AssetConverter.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.AssetConverter = void 0; +exports.AssetConverter = exports.supportedAssetExtensions = void 0; const ProjectFile_1 = require("./ProjectFile"); const fs = require("fs-extra"); const path = require("path"); @@ -8,6 +8,12 @@ const log = require("./log"); const chokidar = require("chokidar"); const crypto = require("crypto"); const Throttle = require("promise-parallel-throttle"); +exports.supportedAssetExtensions = [ + '.png', '.jpg', '.jpeg', '.hdr', + '.ogg', '.mp3', '.flac', '.wav', + '.mp4', '.webm', '.mov', '.wmv', '.avi', + '.ttf' +]; class AssetConverter { constructor(exporter, options, assetMatchers) { this.exporter = exporter; @@ -98,37 +104,43 @@ class AssetConverter { } const ext = fileinfo.ext.toLowerCase(); log.info('Reexporting ' + outPath + ext); - switch (ext) { - case '.png': - case '.jpg': - case '.jpeg': - case '.hdr': - { } - await this.exporter.copyImage(this.platform, file, outPath, {}, {}); + let result = null; + const assetType = AssetConverter.getExtensionAssetType(ext); + switch (assetType) { + case 'image': + result = await this.exporter.copyImage(this.platform, file, outPath, {}, {}); break; - case '.ogg': - case '.mp3': - case '.flac': - case '.wav': { + case 'sound': if (!this.canDecodeFormat(ext)) { log.error(`Error: ${fileinfo.base} should be in wav format, or use \`--ffmpeg path/to/ffmpeg\` option to convert ogg/mp3/flac files`); } - await this.exporter.copySound(this.platform, file, outPath, {}); + result = await this.exporter.copySound(this.platform, file, outPath, {}); break; - } - case '.mp4': - case '.webm': - case '.mov': - case '.wmv': - case '.avi': { - await this.exporter.copyVideo(this.platform, file, outPath, {}); + case 'video': + result = await this.exporter.copyVideo(this.platform, file, outPath, {}); break; - } - case '.ttf': - await this.exporter.copyFont(this.platform, file, outPath, {}); + case 'font': + result = await this.exporter.copyFont(this.platform, file, outPath, {}); + break; + case 'blob': + result = await this.exporter.copyBlob(this.platform, file, outPath + ext, {}); + break; + case 'shader': break; - default: - await this.exporter.copyBlob(this.platform, file, outPath + ext, {}); + } + if (result && this.onAssetChanged) { + const isKeepExt = assetType === 'blob'; + const exportInfo = AssetConverter.createExportInfo(fileinfo, isKeepExt, options, this.exporter.options.from); + this.onAssetChanged({ + name: exportInfo.name, + from: file, + type: assetType, + files: result.files, + file_sizes: result.sizes, + original_width: options.original_width, + original_height: options.original_height, + readable: options.readable + }); } for (let callback of ProjectFile_1.Callbacks.postAssetReexporting) { callback(outPath + ext); @@ -149,6 +161,15 @@ class AssetConverter { } }); } + this.watcher.on('unlink', (file) => { + if (!this.onAssetRemoved) + return; + const fileinfo = path.parse(file); + const ext = fileinfo.ext.toLowerCase(); + const keepExt = !exports.supportedAssetExtensions.includes(ext); + const exportInfo = AssetConverter.createExportInfo(fileinfo, keepExt, options, this.exporter.options.from); + this.onAssetRemoved(exportInfo.name); + }); this.watcher.on('ready', async () => { ready = true; let parsedFiles = []; @@ -161,11 +182,9 @@ class AssetConverter { let fileinfo = path.parse(file); log.info('Exporting asset ' + (index + 1) + ' of ' + files.length + ' (' + fileinfo.base + ').'); const ext = fileinfo.ext.toLowerCase(); - switch (ext) { - case '.png': - case '.jpg': - case '.jpeg': - case '.hdr': { + const assetType = AssetConverter.getExtensionAssetType(ext); + switch (assetType) { + case 'image': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let images; if (options.noprocessing) { @@ -179,10 +198,7 @@ class AssetConverter { } break; } - case '.ogg': - case '.mp3': - case '.flac': - case '.wav': { + case 'sound': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let sounds; if (options.noprocessing) { @@ -203,7 +219,7 @@ class AssetConverter { } break; } - case '.ttf': { + case 'font': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let fonts; if (options.noprocessing) { @@ -217,11 +233,7 @@ class AssetConverter { } break; } - case '.mp4': - case '.webm': - case '.mov': - case '.wmv': - case '.avi': { + case 'video': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let videos; if (options.noprocessing) { @@ -238,7 +250,7 @@ class AssetConverter { } break; } - default: { + case 'blob': { let exportInfo = AssetConverter.createExportInfo(fileinfo, true, options, this.exporter.options.from); let blobs = await this.exporter.copyBlob(this.platform, file, exportInfo.destination, options); if (!options.notinlist) { @@ -274,6 +286,30 @@ class AssetConverter { }); }); } + static getExtensionAssetType(ext) { + switch (ext) { + case '.png': + case '.jpg': + case '.jpeg': + case '.hdr': + return 'image'; + case '.ogg': + case '.mp3': + case '.flac': + case '.wav': + return 'sound'; + case '.mp4': + case '.webm': + case '.mov': + case '.wmv': + case '.avi': + return 'video'; + case '.ttf': + return 'font'; + default: + return 'blob'; + } + } async run(watch, temp) { let files = []; for (let matcher of this.assetMatchers) { diff --git a/out/main.js b/out/main.js index f9e0a373..149099a2 100644 --- a/out/main.js +++ b/out/main.js @@ -287,11 +287,9 @@ async function exportKhaProject(options) { let exporter = null; let target = options.target.toLowerCase(); let baseTarget = target; - let customTarget = null; - if (project.customTargets.get(options.target)) { - customTarget = project.customTargets.get(options.target); + const customTarget = project.customTargets.get(options.target); + if (customTarget) baseTarget = customTarget.baseTarget; - } switch (baseTarget) { case Platform_1.Platform.Krom: exporter = new KromExporter_1.KromExporter(options); @@ -370,9 +368,47 @@ async function exportKhaProject(options) { for (let callback of ProjectFile_1.Callbacks.preAssetConversion) { callback(); } + function writeResourcesJson(files) { + fs.outputFileSync(path.join(options.to, exporter.sysdir() + '-resources', 'files.json'), JSON.stringify({ files: files }, null, '\t')); + } let assetConverter = new AssetConverter_1.AssetConverter(exporter, options, project.assetMatchers); lastAssetConverter = assetConverter; let assets = await assetConverter.run(options.watch, temp); + if (options.watch) { + assetConverter.onAssetChanged = (changedAsset) => { + const fixedName = fixName(changedAsset.name); + const filesI = files.findIndex(f => f.name === fixedName); + const entry = { + name: fixedName, + files: changedAsset.files, + file_sizes: changedAsset.file_sizes, + type: changedAsset.type + }; + if (changedAsset.type === 'image') { + entry.original_width = changedAsset.original_width; + entry.original_height = changedAsset.original_height; + if (changedAsset.readable) + entry.readable = changedAsset.readable; + } + if (filesI >= 0) { + // update existing asset + files[filesI] = entry; + } + else { + files.push(entry); + sortFiles(files); + } + writeResourcesJson(files); + }; + assetConverter.onAssetRemoved = (name) => { + const fixedName = fixName(name); + const filesI = files.findIndex(f => f.name === fixedName); + if (filesI >= 0) { + files.splice(filesI, 1); + writeResourcesJson(files); + } + }; + } if ((target === Platform_1.Platform.DebugHTML5 && process.platform === 'win32') || target === Platform_1.Platform.HTML5) { Icon.exportIco(project.icon, path.join(options.to, exporter.sysdir(), 'favicon.ico'), options.from, options); } @@ -443,9 +479,9 @@ async function exportKhaProject(options) { } return fallback; } - let files = []; + const files = []; for (let asset of assets) { - let file = { + const file = { name: fixName(asset.name), files: asset.files, file_sizes: asset.file_sizes, @@ -475,13 +511,7 @@ async function exportKhaProject(options) { }); } // Sort to prevent files.json from changing between makes when no files have changed. - files.sort(function (a, b) { - if (a.name > b.name) - return 1; - if (a.name < b.name) - return -1; - return 0; - }); + sortFiles(files); function secondPass() { // First pass is for main project files. Second pass is for shaders. // Will try to look for the folder, e.g. 'build/Shaders'. @@ -500,13 +530,22 @@ async function exportKhaProject(options) { }*/ } if (foundProjectFile) { - fs.outputFileSync(path.join(options.to, exporter.sysdir() + '-resources', 'files.json'), JSON.stringify({ files: files }, null, '\t')); + writeResourcesJson(files); } for (let callback of ProjectFile_1.Callbacks.preHaxeCompilation) { callback(); } return await exportProjectFiles(project.name, path.join(options.to, exporter.sysdir() + '-resources'), options, exporter, kore, korehl, project.icon, project.libraries, project.targetOptions, project.defines, project.cdefines, project.cflags, project.cppflags, project.stackSize, project.version, project.id); } +function sortFiles(files) { + files.sort(function (a, b) { + if (a.name > b.name) + return 1; + if (a.name < b.name) + return -1; + return 0; + }); +} function isKhaProject(directory, projectfile) { return fs.existsSync(path.join(directory, 'Kha')) || fs.existsSync(path.join(directory, projectfile)); } diff --git a/src/AssetConverter.ts b/src/AssetConverter.ts index ee3023db..f254a204 100644 --- a/src/AssetConverter.ts +++ b/src/AssetConverter.ts @@ -1,7 +1,7 @@ -import {Callbacks} from './ProjectFile'; +import { Callbacks } from './ProjectFile'; import * as fs from 'fs-extra'; import * as path from 'path'; -import {KhaExporter} from './Exporters/KhaExporter'; +import { KhaExporter } from './Exporters/KhaExporter'; import * as log from './log'; import * as chokidar from 'chokidar'; import * as crypto from 'crypto'; @@ -9,12 +9,39 @@ import * as Throttle from 'promise-parallel-throttle'; import { Options } from './Options'; import { AssetMatcher, AssetMatcherOptions } from './Project'; +export type AssetType = 'image' | 'sound' | 'video' | 'font' | 'blob' | 'shader'; + +export type AssetInfo = { + name: string; + from?: string; + type: AssetType; + files: string[]; + file_sizes: number[]; + original_width?: number; + original_height?: number; + readable?: boolean; + + inputs?: any; + outputs?: any; + uniforms?: any; + types?: any; +}; + +export const supportedAssetExtensions = [ + '.png', '.jpg', '.jpeg', '.hdr', + '.ogg', '.mp3', '.flac', '.wav', + '.mp4', '.webm', '.mov', '.wmv', '.avi', + '.ttf' +]; + export class AssetConverter { options: Options; exporter: KhaExporter; platform: string; assetMatchers: Array; - watcher: fs.FSWatcher; + watcher?: fs.FSWatcher; + onAssetChanged?: (asset: AssetInfo) => void; + onAssetRemoved?: (name: string) => void; constructor(exporter: KhaExporter, options: Options, assetMatchers: Array) { this.exporter = exporter; @@ -45,7 +72,7 @@ export class AssetConverter { return pattern.replace(/{name}/g, name).replace(/{ext}/g, fileinfo.ext).replace(dirRegex, dirValue); } - static createExportInfo(fileinfo: path.ParsedPath, keepextension: boolean, options: AssetMatcherOptions, from: string): {name: string, destination: string} { + static createExportInfo(fileinfo: path.ParsedPath, keepextension: boolean, options: AssetMatcherOptions, from: string): { name: string, destination: string } { let nameValue = fileinfo.name; let destination = fileinfo.name; @@ -75,7 +102,7 @@ export class AssetConverter { nameValue = AssetConverter.replacePattern(options.name, nameValue, fileinfo, options, from); } - return {name: nameValue, destination: destination}; + return { name: nameValue, destination: destination }; } canDecodeFormat(ext: string): boolean { @@ -99,8 +126,8 @@ export class AssetConverter { } } - watch(watch: boolean, match: string, temp: string, options: AssetMatcherOptions): Promise<{ name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[]> { - return new Promise<{ name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[]>((resolve, reject) => { + watch(watch: boolean, match: string, temp: string, options: AssetMatcherOptions): Promise { + return new Promise((resolve, reject) => { let ready = false; let files: string[] = []; this.watcher = chokidar.watch(match, { ignored: /[\/\\]\.(svn|git|DS_Store)/, persistent: watch, followSymlinks: false }); @@ -111,48 +138,55 @@ export class AssetConverter { // with subfolders if (options.destination) { // remove trailing slash - const nameBaseDir = options.nameBaseDir.replace(/\/$/, ''); - const lastIndex = options.baseDir.lastIndexOf(nameBaseDir) - const from = path.resolve(options.baseDir.substring(0, lastIndex)); + const nameBaseDir = options.nameBaseDir!.replace(/\/$/, ''); + const lastIndex = options.baseDir!.lastIndexOf(nameBaseDir) + const from = path.resolve(options.baseDir!.substring(0, lastIndex)); outPath = AssetConverter.replacePattern(options.destination, fileinfo.name, fileinfo, options, from); } const ext = fileinfo.ext.toLowerCase(); log.info('Reexporting ' + outPath + ext); - switch (ext) { - case '.png': - case '.jpg': - case '.jpeg': - case '.hdr': {} - await this.exporter.copyImage(this.platform, file, outPath, {}, {}); - break; - case '.ogg': - case '.mp3': - case '.flac': - case '.wav': { + let result: { files: string[], sizes: number[] } | null = null; + const assetType = AssetConverter.getExtensionAssetType(ext); + + switch (assetType) { + case 'image': + result = await this.exporter.copyImage(this.platform, file, outPath, {}, {}); + break; + case 'sound': if (!this.canDecodeFormat(ext)) { log.error(`Error: ${fileinfo.base} should be in wav format, or use \`--ffmpeg path/to/ffmpeg\` option to convert ogg/mp3/flac files`); } - await this.exporter.copySound(this.platform, file, outPath, {}); + result = await this.exporter.copySound(this.platform, file, outPath, {}); break; - } - - case '.mp4': - case '.webm': - case '.mov': - case '.wmv': - case '.avi': { - await this.exporter.copyVideo(this.platform, file, outPath, {}); + case 'video': + result = await this.exporter.copyVideo(this.platform, file, outPath, {}); break; - } - - case '.ttf': - await this.exporter.copyFont(this.platform, file, outPath, {}); + case 'font': + result = await this.exporter.copyFont(this.platform, file, outPath, {}); break; + case 'blob': + result = await this.exporter.copyBlob(this.platform, file, outPath + ext, {}); + break; + case 'shader': + break; + } - default: - await this.exporter.copyBlob(this.platform, file, outPath + ext, {}); + if (result && this.onAssetChanged) { + const isKeepExt = assetType === 'blob'; + const exportInfo = AssetConverter.createExportInfo(fileinfo, isKeepExt, options, this.exporter.options.from); + this.onAssetChanged({ + name: exportInfo.name, + from: file, + type: assetType, + files: result.files, + file_sizes: result.sizes, + original_width: options.original_width, + original_height: options.original_height, + readable: options.readable + }); } + for (let callback of Callbacks.postAssetReexporting) { callback(outPath + ext); } @@ -173,9 +207,17 @@ export class AssetConverter { } }); } + this.watcher.on('unlink', (file: string) => { + if (!this.onAssetRemoved) return; + const fileinfo = path.parse(file); + const ext = fileinfo.ext.toLowerCase(); + const keepExt = !supportedAssetExtensions.includes(ext); + const exportInfo = AssetConverter.createExportInfo(fileinfo, keepExt, options, this.exporter.options.from); + this.onAssetRemoved(exportInfo.name); + }); this.watcher.on('ready', async () => { ready = true; - let parsedFiles: { name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[] = []; + let parsedFiles: AssetInfo[] = []; let cache: any = {}; let cachePath = path.join(temp, 'cache.json'); if (fs.existsSync(cachePath)) { @@ -186,11 +228,9 @@ export class AssetConverter { let fileinfo = path.parse(file); log.info('Exporting asset ' + (index + 1) + ' of ' + files.length + ' (' + fileinfo.base + ').'); const ext = fileinfo.ext.toLowerCase(); - switch (ext) { - case '.png': - case '.jpg': - case '.jpeg': - case '.hdr': { + const assetType = AssetConverter.getExtensionAssetType(ext); + switch (assetType) { + case 'image': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let images: { files: string[], sizes: number[] }; if (options.noprocessing) { @@ -204,10 +244,7 @@ export class AssetConverter { } break; } - case '.ogg': - case '.mp3': - case '.flac': - case '.wav': { + case 'sound': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let sounds: { files: string[], sizes: number[] }; if (options.noprocessing) { @@ -228,7 +265,7 @@ export class AssetConverter { } break; } - case '.ttf': { + case 'font': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let fonts: { files: string[], sizes: number[] }; if (options.noprocessing) { @@ -242,11 +279,7 @@ export class AssetConverter { } break; } - case '.mp4': - case '.webm': - case '.mov': - case '.wmv': - case '.avi': { + case 'video': { let exportInfo = AssetConverter.createExportInfo(fileinfo, false, options, this.exporter.options.from); let videos: { files: string[], sizes: number[] }; if (options.noprocessing) { @@ -263,7 +296,7 @@ export class AssetConverter { } break; } - default: { + case 'blob': { let exportInfo = AssetConverter.createExportInfo(fileinfo, true, options, this.exporter.options.from); let blobs = await this.exporter.copyBlob(this.platform, file, exportInfo.destination, options); if (!options.notinlist) { @@ -297,14 +330,39 @@ export class AssetConverter { } fs.ensureDirSync(temp); - fs.writeFileSync(cachePath, JSON.stringify(cache), { encoding: 'utf8'}); + fs.writeFileSync(cachePath, JSON.stringify(cache), { encoding: 'utf8' }); resolve(parsedFiles); }); }); } - async run(watch: boolean, temp: string): Promise<{ name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[]> { - let files: { name: string, from: string, type: string, files: string[], file_sizes: number[], original_width: number, original_height: number, readable: boolean }[] = []; + static getExtensionAssetType(ext: string): AssetType { + switch (ext) { + case '.png': + case '.jpg': + case '.jpeg': + case '.hdr': + return 'image'; + case '.ogg': + case '.mp3': + case '.flac': + case '.wav': + return 'sound'; + case '.mp4': + case '.webm': + case '.mov': + case '.wmv': + case '.avi': + return 'video'; + case '.ttf': + return 'font'; + default: + return 'blob'; + } + } + + async run(watch: boolean, temp: string): Promise { + let files: AssetInfo[] = []; for (let matcher of this.assetMatchers) { files = files.concat(await this.watch(watch, matcher.match, temp, matcher.options)); } diff --git a/src/main.ts b/src/main.ts index d8da2c0a..31dc4fc6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ import {Platform} from './Platform'; import {Project, Target, Library} from './Project'; import {loadProject, Callbacks} from './ProjectFile'; import {VisualStudioVersion} from './VisualStudioVersion'; -import {AssetConverter} from './AssetConverter'; +import {AssetConverter, AssetInfo} from './AssetConverter'; import {HaxeCompiler} from './HaxeCompiler'; import {ShaderCompiler, CompiledShader} from './ShaderCompiler'; import {KhaExporter} from './Exporters/KhaExporter'; @@ -322,15 +322,12 @@ async function exportKhaProject(options: Options): Promise { let temp = path.join(options.to, 'temp'); fs.ensureDirSync(temp); - let exporter: KhaExporter = null; + let exporter: KhaExporter | null = null; let target = options.target.toLowerCase(); let baseTarget = target; - let customTarget: Target = null; - if (project.customTargets.get(options.target)) { - customTarget = project.customTargets.get(options.target); - baseTarget = customTarget.baseTarget; - } + const customTarget = project.customTargets.get(options.target); + if (customTarget) baseTarget = customTarget.baseTarget; switch (baseTarget) { case Platform.Krom: @@ -419,10 +416,52 @@ async function exportKhaProject(options: Options): Promise { callback(); } + function writeResourcesJson(files: AssetInfo[]) { + fs.outputFileSync( + path.join(options.to, exporter!.sysdir() + '-resources', 'files.json'), + JSON.stringify({ files: files }, null, '\t') + ); + } + let assetConverter = new AssetConverter(exporter, options, project.assetMatchers); lastAssetConverter = assetConverter; let assets = await assetConverter.run(options.watch, temp); + if (options.watch) { + assetConverter.onAssetChanged = changedAsset => { + const fixedName = fixName(changedAsset.name); + const filesI = files.findIndex(f => f.name === fixedName); + const entry: AssetInfo = { + name: fixedName, + files: changedAsset.files, + file_sizes: changedAsset.file_sizes, + type: changedAsset.type + }; + if (changedAsset.type === 'image') { + entry.original_width = changedAsset.original_width; + entry.original_height = changedAsset.original_height; + if (changedAsset.readable) entry.readable = changedAsset.readable; + } + if (filesI >= 0) { + // update existing asset + files[filesI] = entry; + } else { + files.push(entry); + sortFiles(files); + } + writeResourcesJson(files); + }; + + assetConverter.onAssetRemoved = name => { + const fixedName = fixName(name); + const filesI = files.findIndex(f => f.name === fixedName); + if (filesI >= 0) { + files.splice(filesI, 1); + writeResourcesJson(files); + } + }; + } + if ((target === Platform.DebugHTML5 && process.platform === 'win32') || target === Platform.HTML5) { Icon.exportIco(project.icon, path.join(options.to, exporter.sysdir(), 'favicon.ico'), options.from, options); } @@ -503,9 +542,9 @@ async function exportKhaProject(options: Options): Promise { return fallback; } - let files: {name: string, files: string[], file_sizes: number[], type: string, inputs: any[], outputs: any[], uniforms: any[], types: any[]}[] = []; + const files: AssetInfo[] = []; for (let asset of assets) { - let file: any = { + const file: AssetInfo = { name: fixName(asset.name), files: asset.files, file_sizes: asset.file_sizes, @@ -534,11 +573,7 @@ async function exportKhaProject(options: Options): Promise { } // Sort to prevent files.json from changing between makes when no files have changed. - files.sort(function(a: any, b: any) { - if (a.name > b.name) return 1; - if (a.name < b.name) return -1; - return 0; - }); + sortFiles(files); function secondPass() { // First pass is for main project files. Second pass is for shaders. @@ -559,7 +594,7 @@ async function exportKhaProject(options: Options): Promise { } if (foundProjectFile) { - fs.outputFileSync(path.join(options.to, exporter.sysdir() + '-resources', 'files.json'), JSON.stringify({ files: files }, null, '\t')); + writeResourcesJson(files); } for (let callback of Callbacks.preHaxeCompilation) { @@ -570,6 +605,14 @@ async function exportKhaProject(options: Options): Promise { project.libraries, project.targetOptions, project.defines, project.cdefines, project.cflags, project.cppflags, project.stackSize, project.version, project.id); } +function sortFiles(files: AssetInfo[]) { + files.sort(function(a, b) { + if (a.name > b.name) return 1; + if (a.name < b.name) return -1; + return 0; + }); +} + function isKhaProject(directory: string, projectfile: string) { return fs.existsSync(path.join(directory, 'Kha')) || fs.existsSync(path.join(directory, projectfile)); }