forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			326 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			326 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | package kha; | ||
|  | 
 | ||
|  | import haxe.io.Bytes; | ||
|  | import haxe.Unserializer; | ||
|  | 
 | ||
|  | using StringTools; | ||
|  | 
 | ||
|  | private typedef AssetDataObject = { | ||
|  | 	/** File name, given by khamake, used as identifier in `Assets.someList.get()` function **/ | ||
|  | 	var name: String; | ||
|  | 
 | ||
|  | 	/** List of file paths, unified by khamake to single file with `name`. **/ | ||
|  | 	var files: Array<String>; | ||
|  | 
 | ||
|  | 	/** File sizes in bytes **/ | ||
|  | 	var file_sizes: Array<Int>; | ||
|  | 
 | ||
|  | 	/** Can be `image`, `sound`, `blob`, `font` and `video` **/ | ||
|  | 	var type: String; | ||
|  | 
 | ||
|  | 	/** Original file width (only for images) **/ | ||
|  | 	var ?original_width: Int; | ||
|  | 
 | ||
|  | 	/** Original file height (only for images) **/ | ||
|  | 	var ?original_height: Int; | ||
|  | } | ||
|  | 
 | ||
|  | @:forward(name, files, file_sizes, type, original_width, original_height) | ||
|  | private abstract AssetData(AssetDataObject) from AssetDataObject { | ||
|  | 	@:op(a.b) function _get(key: String): Dynamic { | ||
|  | 		return Reflect.getProperty(this, key); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | @:build(kha.internal.AssetsBuilder.build("image")) | ||
|  | private class ImageList { | ||
|  | 	public function new() {} | ||
|  | 
 | ||
|  | 	public function get(name: String): Image { | ||
|  | 		return Reflect.field(this, name); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | @:build(kha.internal.AssetsBuilder.build("sound")) | ||
|  | private class SoundList { | ||
|  | 	public function new() {} | ||
|  | 
 | ||
|  | 	public function get(name: String): Sound { | ||
|  | 		return Reflect.field(this, name); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | @:build(kha.internal.AssetsBuilder.build("blob")) | ||
|  | private class BlobList { | ||
|  | 	public function new() {} | ||
|  | 
 | ||
|  | 	public function get(name: String): Blob { | ||
|  | 		return Reflect.field(this, name); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | @:build(kha.internal.AssetsBuilder.build("font")) | ||
|  | private class FontList { | ||
|  | 	public function new() {} | ||
|  | 
 | ||
|  | 	public function get(name: String): Font { | ||
|  | 		return Reflect.field(this, name); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | @:build(kha.internal.AssetsBuilder.build("video")) | ||
|  | private class VideoList { | ||
|  | 	public function new() {} | ||
|  | 
 | ||
|  | 	public function get(name: String): Video { | ||
|  | 		return Reflect.field(this, name); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | class Assets { | ||
|  | 	public static var images: ImageList = new ImageList(); | ||
|  | 	public static var sounds: SoundList = new SoundList(); | ||
|  | 	public static var blobs: BlobList = new BlobList(); | ||
|  | 	public static var fonts: FontList = new FontList(); | ||
|  | 	public static var videos: VideoList = new VideoList(); | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * Moves from 0 to 1. Use for loading screens. | ||
|  | 	 */ | ||
|  | 	public static var progress: Float; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Loads all assets which were detected by khamake. When running khamake (doing so is Kha's standard build behavior) | ||
|  | 		it creates a files.json in the build/{target}-resources directoy which contains information about all assets which were found. | ||
|  | 
 | ||
|  | 		The `callback` parameter is always called after loading, even when some or all assets had failures. | ||
|  | 
 | ||
|  | 		An optional callback parameter `failed` is called for each asset that failed to load. | ||
|  | 
 | ||
|  | 		The filter parameter can be used to load assets selectively. The Dynamic parameter describes the asset, | ||
|  | 		it contains the very same objects which are listed in files.json. | ||
|  | 
 | ||
|  | 		Additionally by default all sounds are decompressed. The uncompressSoundsFilter can be used to avoid that. | ||
|  | 		Uncompressed sounds can still be played using Audio.stream which is recommended for music. | ||
|  | 	 */ | ||
|  | 	public static function loadEverything(callback: () -> Void, ?filter: (item: AssetData) -> Bool, ?uncompressSoundsFilter: (soundItem: AssetData) -> Bool, | ||
|  | 			?failed: (err: AssetError) -> Void): Void { | ||
|  | 		final lists: Array<Dynamic> = [ImageList, SoundList, BlobList, FontList, VideoList]; | ||
|  | 		final listInstances: Array<Dynamic> = [images, sounds, blobs, fonts, videos]; | ||
|  | 		var fileCount = 0; | ||
|  | 		var byteCount = 0; | ||
|  | 
 | ||
|  | 		for (i in 0...lists.length) { | ||
|  | 			final list = lists[i]; | ||
|  | 			for (file in Type.getInstanceFields(list)) { | ||
|  | 				if (file.endsWith("Description")) { | ||
|  | 					fileCount++; | ||
|  | 				} | ||
|  | 				else if (file.endsWith("Size")) { | ||
|  | 					var size: Int = Reflect.field(listInstances[i], file); | ||
|  | 					byteCount += size; | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (fileCount == 0) { | ||
|  | 			callback(); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		var filesLeft = fileCount; | ||
|  | 		var bytesLeft = byteCount; | ||
|  | 
 | ||
|  | 		function onLoaded(bytes: Int): Void { | ||
|  | 			filesLeft--; | ||
|  | 			bytesLeft -= bytes; | ||
|  | 			progress = 1 - (bytesLeft / byteCount); | ||
|  | 			if (filesLeft == 0) | ||
|  | 				callback(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function onError(err: AssetError, bytes: Int): Void { | ||
|  | 			reporter(failed)(err); | ||
|  | 			onLoaded(bytes); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		function loadFunc(desc: Dynamic, done: (bytes: Int) -> Void, failure: (err: AssetError, bytes: Int) -> Void): Void { | ||
|  | 			final name = desc.name; | ||
|  | 			final size = desc.file_sizes[0]; | ||
|  | 			switch (desc.type) { | ||
|  | 				case "image": | ||
|  | 					Assets.loadImage(name, function(image: Image) done(size), function(err: AssetError) { | ||
|  | 						onError(err, size); | ||
|  | 					}); | ||
|  | 				case "sound": | ||
|  | 					Assets.loadSound(name, function(sound: Sound) { | ||
|  | 						if (uncompressSoundsFilter == null || uncompressSoundsFilter(desc)) { | ||
|  | 							sound.uncompress(function() { | ||
|  | 								done(size); | ||
|  | 							}); | ||
|  | 						} | ||
|  | 						else { | ||
|  | 							done(size); | ||
|  | 						} | ||
|  | 					}, function(err: AssetError) { | ||
|  | 						onError(err, size); | ||
|  | 					}); | ||
|  | 				case "blob": | ||
|  | 					Assets.loadBlob(name, function(blob: Blob) done(size), function(err: AssetError) { | ||
|  | 						onError(err, size); | ||
|  | 					}); | ||
|  | 				case "font": | ||
|  | 					Assets.loadFont(name, function(font: Font) done(size), function(err: AssetError) { | ||
|  | 						onError(err, size); | ||
|  | 					}); | ||
|  | 				case "video": | ||
|  | 					Assets.loadVideo(name, function(video: Video) done(size), function(err: AssetError) { | ||
|  | 						onError(err, size); | ||
|  | 					}); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		for (i in 0...lists.length) { | ||
|  | 			final list = lists[i]; | ||
|  | 			final listInstance = listInstances[i]; | ||
|  | 			for (field in Type.getInstanceFields(list)) { | ||
|  | 				if (!field.endsWith("Description")) | ||
|  | 					continue; | ||
|  | 				final desc = Reflect.field(listInstance, field); | ||
|  | 				if (filter == null || filter(desc)) { | ||
|  | 					loadFunc(desc, onLoaded, onError); | ||
|  | 				} | ||
|  | 				else { | ||
|  | 					onLoaded(desc.file_sizes[0]); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * Loads an image by name which was preprocessed by khamake. | ||
|  | 	 * | ||
|  | 	 * @param	name The name as defined by the khafile. | ||
|  | 	 * @param	done A callback. | ||
|  | 	 */ | ||
|  | 	public static function loadImage(name: String, done: Image->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = Reflect.field(images, name + "Description"); | ||
|  | 		if (description == null) { | ||
|  | 			reporter(failed, pos)({url: name, error: "Name not found"}); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		LoaderImpl.loadImageFromDescription(description, function(image: Image) { | ||
|  | 			Reflect.setField(images, name, image); | ||
|  | 			done(image); | ||
|  | 		}, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * Loads an image from a path. Most targets support PNG and JPEG formats. | ||
|  | 	 * | ||
|  | 	 * @param	path The path to the image file. | ||
|  | 	 * @param   readable If true, a copy of the image will be kept in main memory for image read operations. | ||
|  | 	 * @param	done A callback. | ||
|  | 	 */ | ||
|  | 	public static function loadImageFromPath(path: String, readable: Bool, done: Image->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = {files: [path], readable: readable}; | ||
|  | 		LoaderImpl.loadImageFromDescription(description, done, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static var imageFormats(get, null): Array<String>; | ||
|  | 
 | ||
|  | 	static function get_imageFormats(): Array<String> { | ||
|  | 		return LoaderImpl.getImageFormats(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadBlob(name: String, done: Blob->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = Reflect.field(blobs, name + "Description"); | ||
|  | 		if (description == null) { | ||
|  | 			reporter(failed, pos)({url: name, error: "Name not found"}); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		LoaderImpl.loadBlobFromDescription(description, function(blob: Blob) { | ||
|  | 			Reflect.setField(blobs, name, blob); | ||
|  | 			done(blob); | ||
|  | 		}, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadBlobFromPath(path: String, done: Blob->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = {files: [path]}; | ||
|  | 		LoaderImpl.loadBlobFromDescription(description, done, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadSound(name: String, done: Sound->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = Reflect.field(sounds, name + "Description"); | ||
|  | 		if (description == null) { | ||
|  | 			reporter(failed, pos)({url: name, error: "Name not found"}); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		return LoaderImpl.loadSoundFromDescription(description, function(sound: Sound) { | ||
|  | 			Reflect.setField(sounds, name, sound); | ||
|  | 			done(sound); | ||
|  | 		}, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadSoundFromPath(path: String, done: Sound->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = {files: [path]}; | ||
|  | 		return LoaderImpl.loadSoundFromDescription(description, done, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static var soundFormats(get, null): Array<String>; | ||
|  | 
 | ||
|  | 	static function get_soundFormats(): Array<String> { | ||
|  | 		return LoaderImpl.getSoundFormats(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadFont(name: String, done: Font->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = Reflect.field(fonts, name + "Description"); | ||
|  | 		if (description == null) { | ||
|  | 			reporter(failed, pos)({url: name, error: "Name not found"}); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		return LoaderImpl.loadFontFromDescription(description, function(font: Font) { | ||
|  | 			Reflect.setField(fonts, name, font); | ||
|  | 			done(font); | ||
|  | 		}, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadFontFromPath(path: String, done: Font->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = {files: [path]}; | ||
|  | 		return LoaderImpl.loadFontFromDescription(description, done, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static var fontFormats(get, null): Array<String>; | ||
|  | 
 | ||
|  | 	static function get_fontFormats(): Array<String> { | ||
|  | 		return ["ttf"]; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadVideo(name: String, done: Video->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = Reflect.field(videos, name + "Description"); | ||
|  | 		if (description == null) { | ||
|  | 			reporter(failed, pos)({url: name, error: "Name not found"}); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		return LoaderImpl.loadVideoFromDescription(description, function(video: Video) { | ||
|  | 			Reflect.setField(videos, name, video); | ||
|  | 			done(video); | ||
|  | 		}, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function loadVideoFromPath(path: String, done: Video->Void, ?failed: AssetError->Void, ?pos: haxe.PosInfos): Void { | ||
|  | 		var description = {files: [path]}; | ||
|  | 		return LoaderImpl.loadVideoFromDescription(description, done, reporter(failed, pos)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static var videoFormats(get, null): Array<String>; | ||
|  | 
 | ||
|  | 	static function get_videoFormats(): Array<String> { | ||
|  | 		return LoaderImpl.getVideoFormats(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function reporter(custom: AssetError->Void, ?pos: haxe.PosInfos) { | ||
|  | 		return custom != null ? custom : haxe.Log.trace.bind(_, pos); | ||
|  | 	} | ||
|  | } |