Skip to content

Commit

Permalink
feat(*): add support for reading/exporting the build manifest alongsi…
Browse files Browse the repository at this point in the history
…de the assetbundle
  • Loading branch information
toverux committed Jan 13, 2018
1 parent cfc5e13 commit f81dab1
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 35 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"devDependencies": {
"@types/chai": "^4.1.0",
"@types/fs-extra": "^5.0.0",
"@types/js-yaml": "^3.10.1",
"@types/mocha": "^2.2.40",
"@types/node": "^9.3.0",
"chai": "^4.1.2",
Expand All @@ -27,6 +28,7 @@
},
"dependencies": {
"@mitm/unityinvoker": "^1.0.0",
"fs-extra": "^5.0.0"
"fs-extra": "^5.0.0",
"js-yaml": "^3.10.0"
}
}
34 changes: 29 additions & 5 deletions src/assets_bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export interface IBuildOptionsMap {
disableLoadAssetByFileNameWithExtension?: boolean;
}

export interface IExportOptions {
overwrite?: boolean;
manifestFile?: streamMaker.WritableFileInput;
}

export class AssetsBundler {
private logger: logger.SimpleLogger = logger.noopLogger;
private unityLogger: logger.SimpleLogger = logger.noopLogger;
Expand Down Expand Up @@ -103,18 +108,23 @@ export class AssetsBundler {

public async to(
file: streamMaker.WritableFileInput,
{ overwrite }: { overwrite: boolean } = { overwrite: true }
): Promise<void> {
options: IExportOptions = {}): Promise<unityproj.IAssetBundleManifest> {

if (!this.buildTarget) {
throw new Error('You must set a build target by calling targeting() before calling to().');
}

const defaultedOptions = { overwrite: true, ...options };

this.state = BundlerState.Bundling;

//=> Normalize dest to a writable strem
//=> Normalize destinations to writable streams
const fileStream = streamMaker.normalizeWriteStream(file);
const fileName = path.basename(fileStream.path.toString());

const hasManifest = !!defaultedOptions.manifestFile;
const manifestStream = hasManifest ? streamMaker.normalizeWriteStream(defaultedOptions.manifestFile!) : null;

//=> Create the build context (contains infos about the paths used by the current build)
const buildContext = new BuildContext(fileName);

Expand All @@ -123,6 +133,8 @@ export class AssetsBundler {
process.on('SIGINT', signalCleanup);
process.on('SIGTERM', signalCleanup);

let manifest: unityproj.IAssetBundleManifest;

try {
//=> Create project and temporary "sub project"
//---------------------------------------------
Expand All @@ -134,16 +146,18 @@ export class AssetsBundler {
//=> Copy original assets and scripts into the project (Unity limitation)
//-----------------------------------------------------------------------
this.logger(`Copying assets to ${buildContext.assetsDir}`);

await unityproj.copyAssetsInProject(buildContext, this.assetsStreams);

this.logger(`Copying custom editor scripts to ${buildContext.editorScriptsDir}`);

await unityproj.copyEditorScriptsInProject(buildContext, this.editorScriptsStreams);

//=> Generate the asset bundle
//----------------------------
this.logger(`Generating asset bundle in ${buildContext.assetBundleDir}`);

await unityproj.generateAssetBundle(
manifest = await unityproj.generateAssetBundle(
buildContext,
this.assetsStreams,
this.buildOptions,
Expand All @@ -155,7 +169,12 @@ export class AssetsBundler {
//=> Move the generated asset bundle to the final dest
//----------------------------------------------------
this.logger(`Moving asset bundle to target destination`);
await unityproj.moveGeneratedAssetBundle(buildContext, fileStream, overwrite);

await unityproj.moveGeneratedAssetBundle(
buildContext,
fileStream, manifestStream,
defaultedOptions.overwrite
);
} finally {
//=> Success or error doesn't matter, we have to cleanup!
//-------------------------------------------------------
Expand All @@ -169,16 +188,21 @@ export class AssetsBundler {
//------
this.state = BundlerState.Dead;
this.logger('Done.');

return manifest;
}

private async cleanup(context: BuildContext): Promise<void> {
this.logger('Cleaning up the Unity project');

await unityproj.cleanupProject(context);
}

private async signalCleanup(context: BuildContext): Promise<void> {
await this.logger('AssetBundle conversion cancelled by user!');

await this.cleanup(context);

process.exit(0);
}

Expand Down
10 changes: 6 additions & 4 deletions src/build_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export class BuildContext {
public readonly editorScriptsDir: string;
public readonly assetBundleDir: string;
public readonly assetBundlePath: string;
public readonly assetBundleManifestPath: string;

public constructor(public readonly assetBundleName: string) {
this.projectRootDir = ProjectDirectory;
this.assetsDir = path.resolve(`${ProjectDirectory}/Assets/CopiedAssets`);
this.projectRootDir = ProjectDirectory;
this.assetsDir = path.resolve(`${ProjectDirectory}/Assets/CopiedAssets`);
this.editorScriptsDir = path.resolve(`${ProjectDirectory}/Assets/Editor/CopiedScripts`);
this.assetBundleDir = path.resolve(`${ProjectDirectory}/GeneratedAssetBundles`);
this.assetBundlePath = path.resolve(`${this.assetBundleDir}/${assetBundleName}`);
this.assetBundleDir = path.resolve(`${ProjectDirectory}/GeneratedAssetBundles`);
this.assetBundlePath = path.resolve(`${this.assetBundleDir}/${assetBundleName}`);
this.assetBundleManifestPath = path.resolve(`${this.assetBundlePath}.manifest`);
}
}
81 changes: 57 additions & 24 deletions src/unity_project.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { logger } from '@mitm/unityinvoker';
import * as fs from 'fs';
import * as fsx from 'fs-extra';
import * as jsyaml from 'js-yaml';
import * as os from 'os';
import * as path from 'path';
import { BuildContext } from './build_context';
Expand All @@ -9,28 +10,25 @@ import * as unity from './unity_invoker';

export type BuildTarget = (keyof typeof buildTargets) | string;

export interface IAssetBundleManifest {
ManifestFileVersion: number;
CRC: number;
Hashes: {
[HashType: string]: {
serializedVersion: number;
Hash: string;
};
};
HashAppended: number;
ClassTypes: Array<{ Class: number, Script: any }>;
Assets: string[];
Dependencies: any[];
}

export const ProjectDirectory = path.join(os.tmpdir(), 'AssetBundleCompiler');
const CompilerScriptSource = path.resolve(`${__dirname}/../../resources/AssetBundleCompiler.cs`);
const CompilerScriptDest = path.resolve(`${ProjectDirectory}/Assets/Editor/AssetBundleCompiler.cs`);

function copyStreamInDirectory(fileStream: fs.ReadStream, directory: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
const fileName = path.basename(fileStream.path as string);
const fileDestStream = fsx.createWriteStream(path.join(directory, fileName));

fileStream.pipe(fileDestStream);

fileDestStream.on('finish', resolve);
fileDestStream.on('error', reject);
});
}

async function copyStreamsInDirectory(fileStreams: fs.ReadStream[], directory: string): Promise<void> {
const copyTasks = fileStreams.map(stream => copyStreamInDirectory(stream, directory));

await Promise.all(copyTasks);
}

export async function shouldCreateProject(): Promise<boolean> {
try {
await fsx.access(ProjectDirectory, fsx.constants.R_OK | fsx.constants.W_OK);
Expand Down Expand Up @@ -77,7 +75,7 @@ export async function generateAssetBundle(
buildTarget: BuildTarget,
unityLogger?: logger.SimpleLogger,
signalAssetProcessed?: logger.SimpleLogger
): Promise<void> {
): Promise<IAssetBundleManifest> {
const assetNames = fileStreams.map(fileStream => path.basename(fileStream.path as string));

await unity.generateAssetBundle(
Expand All @@ -90,11 +88,16 @@ export async function generateAssetBundle(
unityLogger,
signalAssetProcessed
);

const manifestBuf = await fsx.readFile(context.assetBundleManifestPath);

return jsyaml.safeLoad(manifestBuf.toString());
}

export async function moveGeneratedAssetBundle(
context: BuildContext,
finalDest: fs.WriteStream,
finalManifestDest: fs.WriteStream | null,
overwrite: boolean
): Promise<void> {
if (!overwrite) {
Expand All @@ -104,13 +107,17 @@ export async function moveGeneratedAssetBundle(
} finally { /* pass */ }
}

const tasks: Array<Promise<void>> = [];

const assetBundleStream = fsx.createReadStream(context.assetBundlePath);
tasks.push(copyReadableToWritableStream(assetBundleStream, finalDest));

return new Promise<void>((resolve, reject) => {
assetBundleStream.pipe(finalDest)
.on('finish', () => resolve())
.on('error', (err: Error) => reject(err));
});
if (finalManifestDest) {
const manifestStream = fsx.createReadStream(context.assetBundleManifestPath);
tasks.push(copyReadableToWritableStream(manifestStream, finalManifestDest));
}

await Promise.all(tasks);
}

export async function cleanupProject(context: BuildContext): Promise<void> {
Expand All @@ -120,3 +127,29 @@ export async function cleanupProject(context: BuildContext): Promise<void> {
await fsx.remove(context.assetsDir + '.meta');
await fsx.remove(context.assetBundleDir);
}

function copyStreamInDirectory(fileStream: fs.ReadStream, directory: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
const fileName = path.basename(fileStream.path as string);
const fileDestStream = fsx.createWriteStream(path.join(directory, fileName));

fileStream.pipe(fileDestStream);

fileDestStream.on('finish', resolve);
fileDestStream.on('error', reject);
});
}

async function copyStreamsInDirectory(fileStreams: fs.ReadStream[], directory: string): Promise<void> {
const copyTasks = fileStreams.map(stream => copyStreamInDirectory(stream, directory));

await Promise.all(copyTasks);
}

function copyReadableToWritableStream(readable: fs.ReadStream, writable: fs.WriteStream): Promise<void> {
return new Promise<void>((resolve, reject) => {
readable.pipe(writable)
.on('finish', () => resolve())
.on('error', (err: Error) => reject(err));
});
}
6 changes: 5 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
dependencies:
"@types/node" "*"

"@types/js-yaml@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.10.1.tgz#a4ddaf67fec34151e620cdbdd22e40b64217f577"

"@types/mocha@^2.2.40":
version "2.2.46"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.46.tgz#b04713f7759d1cf752effdaae7b3969e285ebc16"
Expand Down Expand Up @@ -867,7 +871,7 @@ js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"

js-yaml@^3.7.0:
js-yaml@^3.10.0, js-yaml@^3.7.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
dependencies:
Expand Down

0 comments on commit f81dab1

Please sign in to comment.