/// <summary> /// Generates `libwebp.js` to handle WebP decompressor /// </summary> private static void GenerateWebPDecompressor(UTinyBuildOptions options, UTinyBuildResults results) { // Check if project use WebP texture format var module = options.Project.Module.Dereference(options.Project.Registry); var webpUsed = AssetIterator.EnumerateAssets(module) .Select(a => a.Object) .OfType <Texture2D>() .Select(t => UTinyUtility.GetAssetExportSettings(options.Project, t)) .OfType <UTinyTextureSettings>() .Any(s => s.FormatType == TextureFormatType.WebP); // Warn about WebP usages if (options.Project.Settings.IncludeWebPDecompressor) { if (!webpUsed) { Debug.LogWarning("This project does not uses the WebP texture format, but includes the WebP decompressor code. To reduce build size, it is recommended to disable \"Include WebP Decompressor\" in project settings."); } } else // WebP decompressor not included, do not copy to binary dir { if (webpUsed) { Debug.LogWarning("This project uses the WebP texture format, but does not include the WebP decompressor code. The content will not load in browsers that do not natively support the WebP format. To ensure maximum compatibility, enable \"Include WebP Decompressor\" in project settings."); } return; } // Copy libwebp to binary dir var srcFile = Path.Combine(UTinyBuildPipeline.GetToolDirectory("libwebp"), KWebPDecompressorFileName); var dstFile = Path.Combine(results.BinaryFolder.FullName, KWebPDecompressorFileName); File.Copy(srcFile, dstFile); results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(new FileInfo(dstFile)); }
protected override TreeViewItem BuildRoot() { ClearInstanceIds(); var root = new TreeViewItem { id = 0, depth = -1 }; var types = Model.GetTypes(); foreach (var typeReference in types) { var type = typeReference.Dereference(Model.Registry); var module = UTinyUtility.GetModules(Model.Registry, typeReference).FirstOrDefault(); var moduleReference = null != module ? (UTinyModule.Reference)module : UTinyModule.Reference.None; var editable = moduleReference.Id == Model.MainModule.Id; if (null != type) { if (type.TypeCode == UTinyTypeCode.Component && !State.FilterComponents) { continue; } if (type.TypeCode == UTinyTypeCode.Struct && !State.FilterStructs) { continue; } if (type.TypeCode == UTinyTypeCode.Enum && !State.FilterEnums) { continue; } if (!editable && State.FilterProjectOnly) { continue; } } var item = new UTinyTypeTreeViewItem(Model.Registry, Model.MainModule, moduleReference, (UTinyType.Reference)type) { id = GenerateInstanceId(typeReference), Editable = editable }; if (null != type) { foreach (var field in type.Fields) { item.AddChild(new UTinyFieldTreeViewItem(Model.Registry, Model.MainModule, moduleReference, field) { id = GenerateInstanceId(field), icon = UTinyIcons.Variable, depth = 1, Editable = editable }); } } root.AddChild(item); } return(root); }
private static void CreateEntityForAsset(IRegistry registry, UTinyProject project, UTinyEntityGroup entityGroup, UTinyAssetInfo asset) { var @object = asset.Object; UTinyEntity entity = null; if (@object is Texture2D) { var texture = @object as Texture2D; var path = AssetDatabase.GetAssetPath(texture); var importer = (TextureImporter)AssetImporter.GetAtPath(path); entity = registry.CreateEntity(UTinyId.New(), $"{GetAssetEntityPath(typeof(Texture2D))}{asset.Name}"); var image2d = entity.AddComponent(registry.GetImage2DType()); image2d.Refresh(); image2d["imageFile"] = $"ut-asset:{asset.Name}"; var settings = UTinyUtility.GetAssetExportSettings(project, @object) as UTinyTextureSettings; if (settings != null && settings.FormatType == TextureFormatType.JPG && UTinyAssetExporter.TextureExporter.ReallyHasAlpha(texture)) { image2d["maskFile"] = $"ut-asset:{asset.Name}_a"; } image2d["disableSmoothing"] = importer.filterMode == FilterMode.Point; var sprite = AssetDatabase.LoadAllAssetsAtPath(path).OfType <Sprite>().FirstOrDefault(); // @NOTE The `importer.spritePixelsPerUnit` is currently NOT used in the editor... // We ALWAYS draw sprites at 1 pixel to world unit in the editor. // When we switch to using SpriteRenderer as our editor drawer we can just pass `sprite.pixelsPerUnit` directly here. var pixelsToWorldUnits = sprite ? sprite.pixelsPerUnit : 1; image2d["pixelsToWorldUnits"] = 1.0f / pixelsToWorldUnits; } else if (@object is Sprite) { var sprite = (Sprite)@object; entity = registry.CreateEntity(UTinyId.New(), $"{GetAssetEntityPath(typeof(Sprite))}{asset.Name}"); var sprite2d = entity.AddComponent(registry.GetSprite2DType()); sprite2d.Refresh(); sprite2d["image"] = sprite.texture; var region = sprite2d["imageRegion"] as UTinyObject; if (null != region) { region["x"] = sprite.rect.x / sprite.texture.width; region["y"] = sprite.rect.y / sprite.texture.height; region["width"] = sprite.rect.width / sprite.texture.width; region["height"] = sprite.rect.height / sprite.texture.height; } var pivot = sprite2d["pivot"] as UTinyObject; if (null != pivot) { pivot["x"] = sprite.pivot.x / sprite.rect.width; pivot["y"] = sprite.pivot.y / sprite.rect.height; } } else if (@object is AudioClip) { entity = registry.CreateEntity(UTinyId.New(), $"{GetAssetEntityPath(typeof(AudioClip))}{asset.Name}"); var audioClip = entity.AddComponent(registry.GetAudioClipType()); audioClip.Refresh(); audioClip["file"] = $"ut-asset:{asset.Name}"; } else if (@object is Font) { entity = registry.CreateEntity(UTinyId.New(), $"{GetAssetEntityPath(typeof(Font))}{asset.Name}"); var fontAsset = entity.AddComponent(registry.GetFontType()); fontAsset.Refresh(); fontAsset["file"] = $"ut-asset:{asset.Name}"; } if (null != entity) { entityGroup.AddEntityReference((UTinyEntity.Reference)entity); } foreach (var child in asset.Children) { CreateEntityForAsset(registry, project, entityGroup, child); } }
/// <summary> /// Generates entry point for the applicaton `main.js` /// This script will contain the system scheduling, window setup and initial group loading /// </summary> private static void GenerateMain(UTinyBuildOptions options, UTinyBuildResults results) { var project = options.Project; var registry = project.Registry; var module = project.Module.Dereference(registry); var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KMainFileName)); var writer = new UTinyCodeWriter(); PrependGeneratedHeader(writer, options.Project.Name); var distVersionFile = new FileInfo("UTiny/version.txt"); var versionString = "internal"; if (distVersionFile.Exists) { versionString = File.ReadAllText(distVersionFile.FullName); } writer.LineFormat("console.log('runtime version: {0}');", versionString) .Line(); var namespaces = new Dictionary <string, string>(); foreach (var m in module.EnumerateDependencies()) { if (string.IsNullOrEmpty(m.Namespace)) { continue; } if (m.IsRuntimeIncluded) { writer.Line($"ut.importModule({m.Namespace});"); continue; } string content; namespaces.TryGetValue(m.Namespace, out content); content += m.Documentation.Summary; namespaces[m.Namespace] = content; } UTinyJsdoc.WriteType(writer, "ut.World", "Singleton world instance"); writer.Line("var world;"); using (writer.Scope("ut.main = function()")) { // Create and setup the world writer .Line("world = new ut.World();") .Line("var options = WorldSetup(world);"); // Write configurations var context = new EntityGroupSetupVisitor.VisitorContext { Project = project, Module = project.Module.Dereference(project.Registry), Registry = project.Registry, Writer = writer, EntityIndexMap = null }; var configuration = project.Configuration.Dereference(registry); foreach (var component in configuration.Components) { var moduleContainingType = registry.FindAllByType <UTinyModule>().First(m => m.Types.Contains(component.Type)); if (!module.EnumerateDependencies().Contains(moduleContainingType)) { // Silently ignore components if the module is not included. // This is by design to preserve user data continue; } var type = component.Type.Dereference(component.Registry); var index = ++context.ComponentIndex; writer.Line($"var c{index} = world.config({UTinyBuildPipeline.GetJsTypeName(type)});"); component.Properties.Visit(new EntityGroupSetupVisitor.ComponentVisitor { VisitorContext = context, Path = $"c{index}", }); } // Setup the scheduler writer.Line("var scheduler = world.scheduler();"); // Schedule all systems var systems = project.Module.Dereference(project.Registry).GetSystemExecutionOrder(); foreach (var reference in systems) { var system = reference.Dereference(project.Registry); if (system == null) { Debug.LogWarning($"Can't resolve system named '{reference.Name}' with ID {reference.Id} -- ignoring, you should delete this system"); continue; } var systemModule = UTinyUtility.GetModules(system).FirstOrDefault(); var systemName = UTinyBuildPipeline.GetJsTypeName(systemModule, system); writer.LineFormat("scheduler.schedule({0});", systemName); } // Enable/disable systems foreach (var reference in systems) { var system = reference.Dereference(project.Registry); // By default systems are enabled when scheduled, nothing to write if (system == null || system.Enabled) { continue; } var systemModule = UTinyUtility.GetModules(system).FirstOrDefault(); var systemName = UTinyBuildPipeline.GetJsTypeName(systemModule, system); // @NOTE Disable currently accepts a string and NOT the `ut.System` object writer.LineFormat("scheduler.disable({0});", EscapeJsString(systemName)); } writer.Line("try { ut.Runtime.Service.run(world); } catch (e) { if (e !== 'SimulateInfiniteLoop') throw e; }"); } writer.Line(); using (writer.Scope("function WorldSetup(world)")) { writer.LineFormat("UT_ASSETS_SETUP(world);"); var startupEntityGroup = module.StartupEntityGroup.Dereference(module.Registry); if (null != startupEntityGroup) { writer.Line($"{KEntityGroupNamespace}.{module.Namespace}[\"{module.StartupEntityGroup.Dereference(module.Registry).Name}\"].load(world);"); } else { Debug.LogError($"{UTinyConstants.ApplicationName}: BuildError - No startup group has been set"); } using (writer.Scope("return")) { writer .LineFormat("canvasWidth: {0},", project.Settings.CanvasWidth) .LineFormat("canvasHeight: {0},", project.Settings.CanvasHeight) .LineFormat("canvasAutoResize: {0},", project.Settings.CanvasAutoResize ? "true" : "false"); } #if UNITY_EDITOR_WIN writer.Length -= 2; #else writer.Length -= 1; #endif writer.WriteRaw(";").Line(); } File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(file); }
/// <summary> /// Packages system objects to `systems.js` /// /// All systems and system dependencies are written to this file /// </summary> private static void GenerateSystems(UTinyBuildOptions options, UTinyBuildResults results) { var project = options.Project; var report = results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(); var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KSystemsFileName)); var writer = new UTinyCodeWriter(CodeStyle.JavaScript); PrependGeneratedHeader(writer, options.Project.Name); foreach (var reference in project.Module.Dereference(project.Registry).GetSystemExecutionOrder()) { var system = reference.Dereference(project.Registry); if (system == null) { Debug.LogWarning($"Can't resolve system named '{reference.Name}' with ID {reference.Id} -- ignoring, you should delete this system"); continue; } if (system.External) { continue; } // Fetch the module this system belongs to var systemModule = UTinyUtility.GetModules(system).FirstOrDefault(); if (system.IsRuntimeIncluded) { continue; } var reportSystemPos = writer.Length; UTinyJsdoc.WriteSystem(writer, system); writer.Line($"{UTinyBuildPipeline.GetJsTypeName(systemModule, system)}.update = {UTinyBuildPipeline.GenerateSystemPrefix(system)}"); writer.IncrementIndent(); if (system.IncludeIterator) { writer.Line(UTinyBuildPipeline.GenerateSystemIteratorPrefix(system)); writer.IncrementIndent(); } var text = system.TextAsset ? system.TextAsset.text : string.Empty; if (!string.IsNullOrEmpty(text)) { var lines = text.Split('\n'); foreach (var line in lines) { writer.Line(line); } } if (system.IncludeIterator) { writer.DecrementIndent(); writer.Line("});"); } writer.DecrementIndent(); writer.Line(UTinyBuildPipeline.GenerateSystemSuffix(system)); report.AddChild(AssetDatabase.GetAssetPath(system.TextAsset), Encoding.ASCII.GetBytes(writer.Substring(reportSystemPos)), system.TextAsset); } File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); report.Reset(file); }
/// <summary> /// Packages assets to `assets.js` or `Assets/*.*` /// </summary> private static void PackageAssets(UTinyBuildOptions options, UTinyBuildResults results) { var buildFolder = options.Destination; var binFolder = results.BinaryFolder; // Export assets to the build directory var buildAssetsFolder = new DirectoryInfo(Path.Combine(buildFolder.FullName, "Assets")); buildAssetsFolder.Create(); var export = UTinyAssetExporter.Export(options.Project, buildAssetsFolder); // copy assets to bin AND/OR encode assets to 'assets.js' var binAssetsFolder = new DirectoryInfo(Path.Combine(binFolder.FullName, "Assets")); binAssetsFolder.Create(); var assetsFile = new FileInfo(Path.Combine(binFolder.FullName, KAssetsFileName)); var writer = new UTinyCodeWriter(); PrependGeneratedHeader(writer, options.Project.Name); var reportAssets = results.BuildReport.AddChild(UTinyBuildReport.AssetsNode); var reportJavaScript = reportAssets.AddChild("JavaScript"); using (var jsdoc = new UTinyJsdoc.Writer(writer)) { jsdoc.Type("object"); jsdoc.Desc("Map containing URLs for all assets. If assets are included as base64 blobs, these will be data URLs."); jsdoc.Line("@example var assetUrl = UT_ASSETS[\"MyCustomAsset\"]"); } long totalBase64Size = 0; using (writer.Scope("var UT_ASSETS =")) { var i = 0; foreach (var info in export) { var reportAsset = reportAssets.AddChild(info.AssetInfo.AssetPath, 0, info.AssetInfo.Object); var settings = UTinyUtility.GetAssetExportSettings(options.Project, info.AssetInfo.Object); if (settings.Embedded) { foreach (var file in info.Exported) { var buffer = File.ReadAllBytes(file.FullName); var base64 = Convert.ToBase64String(buffer); var fileExtension = Path.GetExtension(file.FullName).ToLower(); string mimeType; switch (fileExtension) { case ".png": mimeType = "image/png"; break; case ".jpg": case ".jpeg": mimeType = "image/jpeg"; break; case ".webp": mimeType = "image/webp"; break; case ".mp3": mimeType = "audio/mpeg"; break; case ".wav": mimeType = "audio/wav"; break; case ".json": mimeType = "application/json"; break; case ".ttf": mimeType = "font/truetype"; break; default: Debug.LogWarningFormat("Asset {0} has unknown extension, included as text/plain in assets", file); mimeType = "text/plain"; break; } var comma = i != 0 ? "," : ""; writer.Line($"{comma}\"{Path.GetFileNameWithoutExtension(file.Name)}\": \"data:{mimeType};base64,{base64}\""); i++; reportAsset.AddChild(UTinyBuildPipeline.GetRelativePath(file), Encoding.ASCII.GetBytes(base64), info.AssetInfo.Object); totalBase64Size += base64.Length; file.Delete(); } } else { foreach (var file in info.Exported) { var comma = i != 0 ? "," : ""; writer.Line($"{comma}\"{Path.GetFileNameWithoutExtension(file.Name)}\": \"Assets/{file.Name}\""); i++; reportAsset.AddChild(file, info.AssetInfo.Object); } } } } writer.Line(); writer.WriteRaw("var UT_ASSETS_SETUP = "); { var registry = new UTinyRegistry(); UTinyPersistence.LoadAllModules(registry); var entityGroup = UTinyAssetEntityGroupGenerator.Generate(registry, options.Project); EntityGroupSetupVisitor.WriteEntityGroupSetupFunction(writer, options.Project, entityGroup, false, false); } // Write `assets.js` File.WriteAllText(assetsFile.FullName, writer.ToString()); reportJavaScript.Item.Size = assetsFile.Length - totalBase64Size; // Remaining assets are binplaced foreach (var info in export) { foreach (var file in info.Exported) { if (!file.Exists) { // this asset has been packaged already continue; } file.MoveTo(Path.Combine(binAssetsFolder.FullName, file.Name)); } } // Clean up the build directory buildAssetsFolder.Delete(true); // if we have no standalone assets, cleanup if (binAssetsFolder.GetFiles().Length <= 0) { binAssetsFolder.Delete(); } }
public void CreateNewEntityGroup() { var entityGroupRef = (UTinyEntityGroup.Reference)Registry.CreateEntityGroup(UTinyId.New(), UTinyUtility.GetUniqueName(m_Context.Module.EntityGroups, "NewEntityGroup")); m_Context.Module.AddEntityGroupReference(entityGroupRef); LoadEntityGroup(entityGroupRef); }
public static string GetJsTypeName(UTinyRegistryObjectBase @object) { Assert.IsNotNull(@object); return(GetJsTypeName(UTinyUtility.GetModules(@object).FirstOrDefault(), @object)); }
protected override TreeViewItem BuildRoot() { Model.ClearIds(); var root = new TreeViewItem { id = 0, depth = -1 }; var systems = Model.GetSystems(); foreach (var systemReference in systems) { var system = systemReference.Dereference(Model.Registry); var module = UTinyUtility.GetModules(Model.Registry, systemReference).FirstOrDefault(); var moduleReference = null != module ? (UTinyModule.Reference)module : UTinyModule.Reference.None; var editable = moduleReference.Id == Model.MainModule.Id; if (system != null) { if (!State.FilterSystems) { continue; } if (!editable && State.FilterProjectOnly) { continue; } } var item = new UTinySystemTreeViewItem(Model.Registry, Model.MainModule, moduleReference, systemReference) { id = GenerateInstanceId(systemReference), Editable = editable }; root.AddChild(item); } var scripts = Model.GetScripts(); foreach (var scriptReference in scripts) { var script = scriptReference.Dereference(Model.Registry); var module = UTinyUtility.GetModules(Model.Registry, scriptReference).FirstOrDefault(); var moduleReference = null != module ? (UTinyModule.Reference)module : UTinyModule.Reference.None; var editable = moduleReference.Id == Model.MainModule.Id; if (script != null) { if (!State.FilterScripts) { continue; } if (!editable && State.FilterProjectOnly) { continue; } } var item = new UTinyScriptTreeViewItem(Model.Registry, Model.MainModule, moduleReference, scriptReference) { id = GenerateInstanceId(scriptReference), Editable = editable }; root.AddChild(item); } return(root); }