diff --git a/GlitchyEditor/content/Scenes/physics2D.scene b/GlitchyEditor/content/Scenes/physics2D.scene index 4922e7c..8fcf040 100644 --- a/GlitchyEditor/content/Scenes/physics2D.scene +++ b/GlitchyEditor/content/Scenes/physics2D.scene @@ -13,13 +13,14 @@ B = 0.328787, A = 1 }, + IsCircle = false, + Sprite = null, UvTransform = { X = 0, Y = 0, Z = 1, W = 1 - }, - IsCircle = false + } }, TransformComponent = { Position = { @@ -75,13 +76,14 @@ B = 0.401485, A = 1 }, + IsCircle = false, + Sprite = null, UvTransform = { X = 0, Y = 0, Z = 1, W = 1 - }, - IsCircle = false + } }, TransformComponent = { Position = { @@ -178,13 +180,14 @@ B = 0, A = 1 }, + IsCircle = true, + Sprite = "Textures//rocket.dds", UvTransform = { X = 0, Y = 0, Z = 1, W = 1 - }, - IsCircle = true + } }, TransformComponent = { Position = { diff --git a/GlitchyEditor/content/Textures/DefaultNormal.png.ass b/GlitchyEditor/content/Textures/DefaultNormal.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/DefaultNormal.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/EditorIcons.dds.ass b/GlitchyEditor/content/Textures/EditorIcons.dds.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/EditorIcons.dds.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/nrm.png.ass b/GlitchyEditor/content/Textures/TestMat/nrm.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/nrm.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_metallic.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_normal.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_roughness.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/White.png.ass b/GlitchyEditor/content/Textures/White.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/White.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/rocket.dds.ass b/GlitchyEditor/content/Textures/rocket.dds.ass new file mode 100644 index 0000000..eef758c --- /dev/null +++ b/GlitchyEditor/content/Textures/rocket.dds.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Point, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/content/Textures/rocket.png.ass b/GlitchyEditor/content/Textures/rocket.png.ass new file mode 100644 index 0000000..1f70168 --- /dev/null +++ b/GlitchyEditor/content/Textures/rocket.png.ass @@ -0,0 +1,23 @@ +{ + AssetLoader = "EditorTextureAssetLoader", + Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _samplerStateDescription = { + MinFilter = .Linear, + MagFilter = .Linear, + MipFilter = .Linear, + ComparisonFunction = .Never, + AddressModeU = .Clamp, + AddressModeV = .Clamp, + AddressModeW = .Clamp, + MipMinLOD = -340282346638528859811704183484516925440, + MipMaxLOD = 340282346638528859811704183484516925440, + MaxAnisotropy = 1, + BorderColor = { + R = 1, + G = 1, + B = 1, + A = 1 + } + } + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/AssetFile.bf b/GlitchyEditor/src/AssetFile.bf new file mode 100644 index 0000000..b931777 --- /dev/null +++ b/GlitchyEditor/src/AssetFile.bf @@ -0,0 +1,111 @@ +using System; +using GlitchyEngine; +using System.IO; +using Bon; +using GlitchyEngine.Content; + +namespace GlitchyEditor; + +[BonTarget] +class AssetConfig +{ + [BonIgnore] + public bool IgnoreFile = false; + + [BonInclude] + public String AssetLoader ~ delete _; + + [BonInclude] + public AssetLoaderConfig Config ~ delete _; +} + +class AssetFile +{ + private EditorContentManager _contentManager; + + private String _path; + private String _assetConfigPath; + + private AssetConfig _assetConfig ~ delete _; + + private bool _isDirectory; + + public bool IsDirectory => _isDirectory; + + public StringView FilePath => _path; + + public const String ConfigFileExtension = ".ass"; + + public AssetConfig AssetConfig => _assetConfig; + + [AllowAppend] + public this(EditorContentManager contentManager, StringView path, bool isDirectory) + { + String pathBuffer = append String(path); + String configPathBuffer = append String(path.Length + ConfigFileExtension.Length); + + _path = pathBuffer; + + configPathBuffer..Append(path).Append(ConfigFileExtension); + _assetConfigPath = configPathBuffer; + + _contentManager = contentManager; + + _isDirectory = isDirectory; + + Log.EngineLogger.AssertDebug(File.Exists(_path), "File doesn't exist."); + + FindAssetConfig(); + } + + // Loads the asset config (.ass) file or creates it. + private void FindAssetConfig() + { + if (File.Exists(_assetConfigPath)) + { + LoadAssetConfig(); + } + else + { + CreateDefaultAssetLoader(); + } + } + + private void CreateDefaultAssetLoader() + { + String fileExtension = Path.GetExtension(_path, .. scope .()); + + _assetConfig = new AssetConfig(); + var assetLoader = _contentManager.GetDefaultAssetLoader(fileExtension); + + // We don't have a loader -> we don't need a config + if (assetLoader == null) + return; + + _assetConfig.AssetLoader = new String(); + assetLoader.GetType().GetName(_assetConfig.AssetLoader); + + _assetConfig.Config = assetLoader?.GetDefaultConfig(); + _assetConfig.Config?.[Friend]_changed = true; + + SaveAssetConfig(); + } + + private void LoadAssetConfig() + { + if (Bon.DeserializeFromFile(ref _assetConfig, _assetConfigPath) case .Err) + { + Log.EngineLogger.Error($"Failed to load asset config {_assetConfigPath}"); + + // TODO: Handle failure of asset config loading + Runtime.NotImplemented(); + } + } + + private void SaveAssetConfig() + { + gBonEnv.serializeFlags |= .Verbose; + + Bon.SerializeIntoFile(_assetConfig, _assetConfigPath); + } +} \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/TextureAssetLoader.bf b/GlitchyEditor/src/Assets/TextureAssetLoader.bf new file mode 100644 index 0000000..2be2848 --- /dev/null +++ b/GlitchyEditor/src/Assets/TextureAssetLoader.bf @@ -0,0 +1,154 @@ +using System; +using System.Collections; +using Bon; +using System.IO; +using GlitchyEngine; +using GlitchyEngine.Content; +using GlitchyEngine.Renderer; +using GlitchyEngine.Math; +using DirectXTK; + +namespace GlitchyEditor.Assets; + +[BonTarget, BonPolyRegister] +class EditorTextureAssetLoaderConfig : AssetLoaderConfig +{ + [BonInclude] + private bool _generateMipMaps; + + [BonInclude] + private SamplerStateDescription _samplerStateDescription = .(); + + public bool GenerateMipMaps + { + get => _generateMipMaps; + set => SetIfChanged(ref _generateMipMaps, value); + } + + public SamplerStateDescription SamplerStateDescription + { + get => _samplerStateDescription; + set => SetIfChanged(ref _samplerStateDescription, value); + } +} + +class EditorTextureAssetLoader : IAssetLoader +{ + private static readonly List _fileExtensions = new .(){".png", ".dds"} ~ delete _; // ".jpg", ".bmp" + + public static List FileExtensions => _fileExtensions; + + public AssetLoaderConfig GetDefaultConfig() + { + return new EditorTextureAssetLoaderConfig(); + } + + public IRefCounted LoadAsset(Stream data, AssetLoaderConfig config) + { + var config; + + if (config == null) + { + config = GetDefaultConfig(); + defer:: delete config; + } + + Log.EngineLogger.AssertDebug(config is EditorTextureAssetLoaderConfig, "config has wrong type."); + + return LoadTexture(data, (EditorTextureAssetLoaderConfig)config); + } + + const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; + const String DdsMagicWord = "DDS "; + + private static Texture LoadTexture(Stream data, EditorTextureAssetLoaderConfig config) + { + Debug.Profiler.ProfileResourceFunction!(); + + var readResult = data.Read(); + + data.Position = 0; + + char8[8] magicWord; + + Texture texture = null; + + if (readResult case .Ok(out magicWord)) + { + StringView strView = .(&magicWord, magicWord.Count); + + if (strView.StartsWith(PngMagicWord)) + { + texture = LoadPng(data, config); + } + else if (strView.StartsWith(DdsMagicWord)) + { + texture = LoadDds(data, config); + } + else + { + Runtime.FatalError("Unknown image format."); + } + } + + Log.EngineLogger.AssertDebug(texture != null); + + /*SamplerStateDescription samplerDesc = .() + { + MinFilter = config.MinFilter, + MagFilter = config.MagFilter, + MipFilter = config.MipFilter, + AddressModeU = config.WrapModeU, + AddressModeV = config.WrapModeV, + AddressModeW = config.WrapModeW + };*/ + + SamplerState sam = SamplerStateManager.GetSampler(config.SamplerStateDescription); + + texture.SamplerState = sam; + + sam.ReleaseRef(); + + return texture; + } + + private static Texture LoadPng(Stream data, EditorTextureAssetLoaderConfig config) + { + Debug.Profiler.ProfileResourceFunction!(); + + uint8[] pngData = new:ScopedAlloc! uint8[data.Length]; + + var result = data.TryRead(pngData); + + if (result case .Err(let err)) + { + Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}"); + } + + uint8* rawData = ?; + uint32 width = 0, height = 0; + + uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count); + + Log.EngineLogger.Assert(errorCode == 0, "Failed to load png File"); + + // TODO: load as SRGB because PNGs are usually not stored as linear + //Texture2DDesc desc = .(width, height, srgb? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable); + Texture2DDesc desc = .(width, height, .R8G8B8A8_UNorm, 1, 1, .Immutable); + Texture2D texture = new Texture2D(desc); + texture.SetData((.)rawData); + + // TODO: Generate mip maps + + LodePng.LodePng.Free(rawData); + + return texture; + } + + private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config) + { + Texture2D texture = new [Friend]Texture2D(data); + + return texture; + } +} diff --git a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf index 9307e55..ef610c0 100644 --- a/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf +++ b/GlitchyEditor/src/EditWindows/ComponentEditWindow.bf @@ -268,7 +268,7 @@ namespace GlitchyEditor.EditWindows StringView path = .((char8*)payload.Data, (int)payload.DataSize); - Texture2D texture = new Texture2D(path, true); + Texture2D texture = Content.LoadAsset(path);//new Texture2D(path, true); spriteRendererComponent.Sprite?.ReleaseRef(); spriteRendererComponent.Sprite = texture; @@ -319,7 +319,7 @@ namespace GlitchyEditor.EditWindows StringView path = .((char8*)payload.Data, (int)payload.DataSize); - using (Texture2D newTexture = new Texture2D(path, true)) + using (Texture2D newTexture = Content.LoadAsset(path))//new Texture2D(path, true)) { newTexture.SamplerState = SamplerStateManager.AnisotropicWrap; material.SetTexture(texture.key, newTexture); diff --git a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf index f4ece72..f4e2bba 100644 --- a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf +++ b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf @@ -16,16 +16,16 @@ namespace GlitchyEditor.EditWindows const String ContentDirectory = "./content"; //private String _currentDirectory ~ delete _; - private TreeNode _currentDirectoryNode; + private TreeNode _currentDirectoryNode; public static SubTexture2D s_FolderTexture; public static SubTexture2D s_FileTexture; - public EditorContentManager _manager ~ delete _; + public EditorContentManager _manager; - public this() + public this(EditorContentManager contentManager) { - _manager = new EditorContentManager(); + _manager = contentManager; } protected override void InternalShow() diff --git a/GlitchyEditor/src/Editor.bf b/GlitchyEditor/src/Editor.bf index fca76d2..87df92a 100644 --- a/GlitchyEditor/src/Editor.bf +++ b/GlitchyEditor/src/Editor.bf @@ -5,6 +5,7 @@ using System.Collections; using GlitchyEngine.Collections; using GlitchyEditor.EditWindows; using GlitchyEngine; +using GlitchyEditor.Assets; namespace GlitchyEditor { @@ -15,7 +16,7 @@ namespace GlitchyEditor private EntityHierarchyWindow _entityHierarchyWindow ~ delete _; private ComponentEditWindow _componentEditWindow ~ delete _; private SceneViewportWindow _sceneViewportWindow = new .(this) ~ delete _; - private ContentBrowserWindow _contentBrowserWindow = new .() ~ delete _; + private ContentBrowserWindow _contentBrowserWindow ~ delete _; public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow; public ComponentEditWindow ComponentEditWindow => _componentEditWindow; @@ -43,6 +44,8 @@ namespace GlitchyEditor CurrentScene = scene; _componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow); + + _contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager); } public void Update() diff --git a/GlitchyEditor/src/EditorContentManager.bf b/GlitchyEditor/src/EditorContentManager.bf index b41ac54..0db767f 100644 --- a/GlitchyEditor/src/EditorContentManager.bf +++ b/GlitchyEditor/src/EditorContentManager.bf @@ -5,6 +5,9 @@ using System.Collections; using GlitchyEngine.Renderer; using System.Threading; using GlitchyEngine.Content; +using GlitchyEditor.Assets; +using GlitchyEngine; +using System.Linq; namespace GlitchyEditor; @@ -70,52 +73,71 @@ namespace GlitchyEditor; } }*/ -class EditorContentManager +public class AssetNode { - // TODO: Get from project - const String ContentDirectory = "./content"; + public String Name ~ delete _; + public String Path ~ delete _; + + public bool IsDirectory; + + public AssetFile AssetFile ~ delete _; + + public List SubAssets ~ { + SubAssets?.ClearAndDeleteItems(); + delete SubAssets; + } + + public Texture2D PreviewImage ~ _?.ReleaseRef(); +} + +public class Asset +{ + public AssetNode Asset; + public String Name ~ delete _; + //public String AssetInternalPath ~ delete _; + + public Texture2D PreviewImage ~ _?.ReleaseRef(); +} + +class EditorContentManager : IContentManager +{ + // TODO: Get from workspace + //const String ContentDirectory = "./content"; FileSystemWatcher fsw ~ { _.StopRaisingEvents(); delete _; }; - - bool _fileSystemDirty = true; - public class AssetNode - { - public String Name ~ delete _; - public String Path ~ delete _; - - public bool IsDirectory; - - public List SubAssets ~ { - SubAssets?.ClearAndDeleteItems(); - delete SubAssets; - } - - public Texture2D PreviewImage ~ _?.ReleaseRef(); - } - - public class Asset - { - public AssetNode Asset; - public String Name ~ delete _; - //public String AssetInternalPath ~ delete _; - - public Texture2D PreviewImage ~ _?.ReleaseRef(); - } + bool _fileSystemDirty = false; internal TreeNode _assetHierarchy = new TreeNode() ~ DeleteTreeAndChildren!(_); + private append String _contentDirectory = .(); + + public StringView ContentDirectory => _contentDirectory; + + private append List _identifiers = .() ~ _.ClearAndDeleteItems(); + + private append Dictionary _loadedAssets = .(); // Check if all resources are unloaded + public this() { + } + + public void SetContentDirectory(StringView contentDirectory) + { + _contentDirectory.Clear(); + _contentDirectory.Append(contentDirectory); + _fileSystemDirty = true; + SetupFileSystemWatcher(); } /// Initializes the FSW for the current ContentDirectory and registers the events. private void SetupFileSystemWatcher() { + delete fsw; fsw = new FileSystemWatcher(ContentDirectory); fsw.IncludeSubdirectories = true; @@ -140,6 +162,7 @@ class EditorContentManager public void Update() { + // TODO: do we really need to do this in the update loop? if (_fileSystemDirty) { UpdateFiles(); @@ -148,24 +171,26 @@ class EditorContentManager private void UpdateFiles() { + // TODO: we don't need to rebuild the entire tree! DeleteTreeAndChildren!(_assetHierarchy); + _assetHierarchy = new TreeNode(new AssetNode()); _assetHierarchy->Path = new String(ContentDirectory); _assetHierarchy->Name = new String("Content"); String str = scope .(ContentDirectory); - void GrabSubAssets(TreeNode assetNode) + /*void GrabSubAssets(TreeNode assetNode) { // TODO: Dedicated asset loaders that register their extensions, etc....! if (assetNode->Name.EndsWith(".png", .OrdinalIgnoreCase)) { - assetNode->SubAssets = new List(); - assetNode->SubAssets.Add(new Asset() + assetNode->SubAssets = new:allocator List(); + assetNode->SubAssets.Add(new:allocator Asset() { Asset = assetNode.Value, - Name = new String(assetNode->Name) + Name = new:allocator String(assetNode->Name) }); } else if (assetNode->Name.EndsWith(".gltf", .OrdinalIgnoreCase) || assetNode->Name.EndsWith(".glb", .OrdinalIgnoreCase)) @@ -173,11 +198,11 @@ class EditorContentManager List meshNames = scope List(); ModelLoader.GetMeshNames(assetNode->Path, meshNames); - assetNode->SubAssets = new List(); + assetNode->SubAssets = new:allocator List(); for (var meshName in meshNames) { - assetNode->SubAssets.Add(new Asset() + assetNode->SubAssets.Add(new:allocator Asset() { Asset = assetNode.Value, // Pass ownership of meshName to SubAsset -> save a copy and delete @@ -185,6 +210,11 @@ class EditorContentManager }); } } + }*/ + + void HandleFile(AssetNode node) + { + node.AssetFile = new AssetFile(this, node.Path, node.IsDirectory); } void AddFilesOfDirectory(TreeNode directory) @@ -192,11 +222,18 @@ class EditorContentManager String filter = scope $"{directory->Path}/*"; String buffer = scope String(); + String extensionBuffer = scope String(); for (var entry in Directory.Enumerate(filter, .Files)) { entry.GetFilePath(buffer..Clear()); + Path.GetExtension(buffer, extensionBuffer..Clear()); + + // Don't add meta-files + if (extensionBuffer == ".ass") + continue; + AssetNode e = new AssetNode(); e.Name = new String(); Path.GetFileName(buffer, e.Name); @@ -206,7 +243,8 @@ class EditorContentManager e.IsDirectory = entry.IsDirectory; var node = directory.AddChild(e); - GrabSubAssets(node); + //GrabSubAssets(node); + HandleFile(node.Value); } } @@ -244,4 +282,111 @@ class EditorContentManager _fileSystemDirty = false; } + + public IAssetLoader GetDefaultAssetLoader(StringView fileExtension) + { + if (_defaultAssetLoaders.TryGetValue(fileExtension, let value)) + return value; + + return null; + } + + private append List _supportedExtensions = .() ~ ClearAndDeleteItems!(_); + private append List _assetLoaders = .() ~ ClearAndDeleteItems!(_); + private append Dictionary _defaultAssetLoaders = .(); + + public void RegisterAssetLoader() where T : new, class, IAssetLoader + { + //Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered."); + + _assetLoaders.Add(new T()); + + for (StringView ext in T.FileExtensions) + _supportedExtensions.Add(new String(ext)); + } + + public void SetAsDefaultAssetLoader(params StringView[] fileExtensions) where T : IAssetLoader + { + for (var ext in fileExtensions) + { + // Find file extension in registered file extensions + String foundExtension = null; + + for (var supportedExt in _supportedExtensions) + { + if (supportedExt == ext) + { + foundExtension = supportedExt; + break; + } + } + + Log.EngineLogger.Assert(foundExtension != null, "File Extension is not registered."); + + for (var loader in _assetLoaders) + { + if (loader.GetType() == typeof(T)) + { + _defaultAssetLoaders[foundExtension] = loader; + break; + } + } + } + } + + public bool IsLoaded(StringView identifier) + { + return _loadedAssets.ContainsKey(identifier); + } + + public IRefCounted LoadAsset(StringView identifier) + { + if (_loadedAssets.TryGetValue(identifier, let asset)) + { + return asset..AddRef(); + } + + String filePath = scope String(identifier.Length + _contentDirectory.Length + 2); + Path.InternalCombine(filePath, _contentDirectory, identifier); + + AssetFile file = scope .(this, filePath, false); + + IAssetLoader assetLoader = null; + + String loaderTypeName = scope .(128); + + for (IAssetLoader loader in _assetLoaders) + { + loader.GetType().GetName(loaderTypeName..Clear()); + + if (loaderTypeName == file.AssetConfig.AssetLoader) + { + assetLoader = loader; + break; + } + } + + Log.EngineLogger.AssertDebug(assetLoader != null); + + Stream stream = GetStream(filePath); + + IRefCounted loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config); + + String identifierString = new .(identifier); + _identifiers.Add(identifierString); + _loadedAssets[identifierString] = loadedAsset; + + delete stream; + + return loadedAsset; + } + + // TODO: probably not needed + public Stream GetStream(StringView assetIdentifier) + { + FileStream fs = new FileStream(); + var result = fs.Open(assetIdentifier, .Open, .Read, .ReadWrite); + + return fs; + } } \ No newline at end of file diff --git a/GlitchyEditor/src/EditorIcons.bf b/GlitchyEditor/src/EditorIcons.bf index 686737b..abc9b10 100644 --- a/GlitchyEditor/src/EditorIcons.bf +++ b/GlitchyEditor/src/EditorIcons.bf @@ -24,7 +24,7 @@ namespace GlitchyEditor public this(String texturePath, Vector2 iconSize) { - _texture = new Texture2D(texturePath); + _texture = Content.LoadAsset(texturePath);//new Texture2D(texturePath); Vector2 pen = .(); diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 55d6921..203b33c 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -12,6 +12,7 @@ using System.Collections; using GlitchyEngine.Renderer.Animation; using System.IO; using GlitchyEngine.Core; +using GlitchyEditor.Assets; namespace GlitchyEditor { @@ -70,6 +71,8 @@ namespace GlitchyEditor { Application.Get().Window.IsVSync = false; + InitContentLoader(); + InitGraphics(); _camera = EditorCamera(Vector3(3.5f, 1.25f, 2.75f), Quaternion.FromEulerAngles(MathHelper.ToRadians(40), MathHelper.ToRadians(25), 0), MathHelper.ToRadians(75), 0.1f, 1); @@ -80,6 +83,17 @@ namespace GlitchyEditor NewScene(); } + private void InitContentLoader() + { + EditorContentManager contentManager = new EditorContentManager(); + contentManager.SetContentDirectory("./content"); + + contentManager.RegisterAssetLoader(); + contentManager.SetAsDefaultAssetLoader(".png", ".dds"); + + Application.Get().[Friend]_contentManager = contentManager; + } + private void InitGraphics() { _context = Application.Get().Window.Context..AddRef(); @@ -327,7 +341,7 @@ namespace GlitchyEditor // Just for testing private void TestEntitiesWithModels() { - { + /*{ var lightNtt = _scene.CreateEntity("My Sexy Sun 2"); let transform = lightNtt.GetComponent(); transform.Position = .(0, 0, 0); @@ -449,7 +463,7 @@ namespace GlitchyEditor } ClearAndReleaseItems!(clips); - } + }*/ } /// Creates a new scene. diff --git a/GlitchyEngine/src/Application.bf b/GlitchyEngine/src/Application.bf index b35eeba..0331a42 100644 --- a/GlitchyEngine/src/Application.bf +++ b/GlitchyEngine/src/Application.bf @@ -60,7 +60,7 @@ namespace GlitchyEngine _rendererApi = new RendererAPI(); _rendererApi.Context = _window.Context; - _contentManager = new ContentManager("./content"); + //_contentManager = new ContentManager("./content"); SamplerStateManager.Init(); diff --git a/GlitchyEngine/src/Content/ContentManager.bf b/GlitchyEngine/src/Content/ContentManager.bf index f0f9f73..d091e1e 100644 --- a/GlitchyEngine/src/Content/ContentManager.bf +++ b/GlitchyEngine/src/Content/ContentManager.bf @@ -1,6 +1,8 @@ using System; using System.IO; using xxHash; +using System.Collections; +using Bon; namespace GlitchyEngine.Content { @@ -22,17 +24,95 @@ namespace GlitchyEngine.Content _hash = xxHash.ComputeHash(id); } } + + [BonTarget, BonPolyRegister] + abstract class AssetLoaderConfig + { + [BonIgnore] + protected bool _changed; + + public bool Changed => _changed; + + protected bool SetIfChanged(ref T field, T value) + { + if (field == value) + return false; + + field = value; + _changed = true; + + return true; + } + } + + interface IAssetLoader + { + static List FileExtensions { get; } + + AssetLoaderConfig GetDefaultConfig(); + + /// Loads the asset from the given data stream with the specified config. + /// @param file The stream containing the asset. + /// @param config The configuration which specifies the settings used to load the asset. + /// @returns The loaded asset. + IRefCounted LoadAsset(Stream file, AssetLoaderConfig config); + } + + static + { + /// Loads the specified asset with the given contentManager or the current applications content manager. + public static T LoadAsset(StringView assetIdentifier, IContentManager contentManager = null) where T : IRefCounted + { + var contentManager; + + if (contentManager == null) + contentManager = Application.Get().ContentManager; + + Object o = contentManager.LoadAsset(assetIdentifier); + + Log.EngineLogger.AssertDebug(o is T); + + return (T)o; + } + } interface IContentManager { - void GetFilePath(String outFilename, String filename); + /// Loads the given asset. + IRefCounted LoadAsset(StringView assetIdentifier); - Stream GetFile(String filename); + /// Returns a data stream for the given asset. + Stream GetStream(StringView assetIdentifier); + + void RegisterAssetLoader() where T : new, class, IAssetLoader; + void SetAsDefaultAssetLoader(params StringView[] fileExtensions) where T : IAssetLoader; + //void GetFilePath(String outFilename, String filename); + + //Stream GetFile(String filename); } - class ContentManager : IContentManager + class RuntimeContentManager : IContentManager { - private String _contentRoot; + public IRefCounted LoadAsset(StringView identifier) + { + Runtime.NotImplemented(); + } + + public Stream GetStream(StringView assetIdentifier) + { + Runtime.NotImplemented(); + } + + public void RegisterAssetLoader() where T : IAssetLoader where T : class where T : new + { + Runtime.NotImplemented(); + } + + public void SetAsDefaultAssetLoader(params StringView[] fileExtensions) where T : IAssetLoader + { + Runtime.NotImplemented(); + } + /*private String _contentRoot; [AllowAppend] public this(String contentRoot) @@ -65,6 +145,6 @@ namespace GlitchyEngine.Content } return stream; - } + }*/ } } \ No newline at end of file diff --git a/GlitchyEngine/src/Extension/System/Collections/IEnumerable.bf b/GlitchyEngine/src/Extension/System/Collections/IEnumerable.bf new file mode 100644 index 0000000..f48dd7e --- /dev/null +++ b/GlitchyEngine/src/Extension/System/Collections/IEnumerable.bf @@ -0,0 +1,2927 @@ +// https://github.com/Moneyl/Beef.Linq/blob/master/src/src/Enumerable.bf + +using System.Collections; +using System; +using internal System.Linq; + +namespace System.Linq +{ + public static + { + public static class Enumerable + { + public struct EmptyEnumerable : IEnumerable + { + public Enumerator GetEnumerator() => .(); + + public struct Enumerator : IEnumerator + { + public Result GetNext() + { + return .Err; + } + } + } + + public static EmptyEnumerable Empty() => .(); + + public struct RangeEnumerable : IEnumerable where TSource : operator TSource + int + { + TSource mStart; + TSource mEnd; + + public this(TSource start, TSource end) + { + mStart = start; + mEnd = end; + } + + public Enumerator GetEnumerator() => .(mStart, mEnd); + + public struct Enumerator : IEnumerator + { + TSource mCurrent; + TSource mEnd; + + public this(TSource start, TSource end) + { + mCurrent = start; + mEnd = end; + } + + public Result GetNext() mut + { + if (mCurrent == mEnd) + return .Err; + + let next = mCurrent; + mCurrent = mCurrent + 1; + return .Ok(next); + } + } + } + + public static RangeEnumerable + Range(TSource count) + where TSource : operator TSource + int + { + return .(default, count); + } + + public static RangeEnumerable + Range(TSource start, TSource end) + where TSource : operator TSource + int + where TSource : operator TSource + TSource + { + return .(start, end); + } + + public struct RepeatEnumerable : IEnumerable + { + TSource mValue; + int mCount; + + public this(TSource value, int count) + { + mValue = value; + mCount = count; + } + + public Enumerator GetEnumerator() => .(mValue, mCount); + + public struct Enumerator : IEnumerator + { + TSource mValue; + int mCount; + + public this(TSource value, int count) + { + mValue = value; + mCount = count; + } + + public Result GetNext() mut + { + if (--mCount >= 0) + return .Ok(mValue); + + return .Err; + } + } + } + + public static RepeatEnumerable + Repeat(TSource value, int count) + { + return .(value, count); + } + } + + + #region Matching + public static bool All(this TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + return InternalAll(items, predicate); + } + + public static bool All(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + return InternalAll(items.GetEnumerator(), predicate); + } + + static bool InternalAll(TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + switch (enumerator.GetNext()) + { + case .Ok(let val): if (!predicate(val)) return false; + case .Err: return false; + } + + while (enumerator.GetNext() case .Ok(let val)) + if (!predicate(val)) + return false; + + return true; + } + } + + public static bool Any(this TCollection items) + where TCollection : concrete, IEnumerable + { + for (var it in items) + return true; + + return false; + } + + public static bool Any(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable, class, new + where TPredicate : delegate bool(TSource) + { + for (var it in items) + if (predicate(it)) + return true; + + return false; + } + + public static bool Any(this TEnum items) + where TEnum : concrete, IEnumerator + { + for (var it in items) + return true; + + return false; + } + + public static bool Any(this TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + for (var it in items) + if (predicate(it)) + return true; + + return false; + } + + public static bool Contains(this TCollection items, TSource source) + where TCollection : concrete, IEnumerable + where bool : operator TSource == TSource + { + for (var it in items) + if (it == source) + return true; + + return false; + } + + public static bool Contains(this TEnum items, TSource source) + where TEnum : concrete, IEnumerator + where bool : operator TSource == TSource + { + for (var it in items) + if (it == source) + return true; + + return false; + } + + + public static bool SequenceEquals(this TLeft left, TRight right) + where TLeft : concrete, IEnumerable + where TRight : concrete, IEnumerable + where bool : operator TSource == TSource + { + return InternalSequenceEquals< + decltype(default(TLeft).GetEnumerator()), + decltype(default(TRight).GetEnumerator()), + TSource>(left.GetEnumerator(), right.GetEnumerator()); + } + + public static bool SequenceEquals(this TLeft left, TRight right) + where TLeft : concrete, IEnumerable + where TRight : concrete, IEnumerator + where bool : operator TSource == TSource + { + return InternalSequenceEquals< + decltype(default(TLeft).GetEnumerator()), + TRight, + TSource>(left.GetEnumerator(), right); + } + + public static bool SequenceEquals(this TLeft left, TRight right) + where TLeft : concrete, IEnumerator + where TRight : concrete, IEnumerable + where bool : operator TSource == TSource + { + return InternalSequenceEquals< + TLeft, + decltype(default(TRight).GetEnumerator()), + TSource>(left, right.GetEnumerator()); + } + + public static bool SequenceEquals(this TLeft left, TRight right) + where TLeft : concrete, IEnumerator + where TRight : concrete, IEnumerator + where bool : operator TSource == TSource + { + return InternalSequenceEquals(left, right); + } + + static bool InternalSequenceEquals(TLeft left, TRight right) + where TLeft : concrete, IEnumerator + where TRight : concrete, IEnumerator + where bool : operator TSource == TSource + { + using (var iterator0 = Iterator.Wrap(left)) + { + var e0 = iterator0.mEnum; + using (var iterator1 = Iterator.Wrap(right)) + { + var e1 = iterator1.mEnum; + while (true) + { + switch (e0.GetNext()) + { + case .Ok(let i0): + switch (e1.GetNext()) + { + case .Ok(let i1): + if (i0 != i1) + return false; + case .Err: + return false; + } + case .Err: + return e1.GetNext() case .Err; + } + } + } + } + } + + #endregion + + #region Aggregates + + public static TSource Average(this TCollection items) + where TCollection : concrete, IEnumerable + where TSource : operator TSource / int + where TSource : operator TSource + TSource + { + return InternalAverage(items.GetEnumerator()); + } + + public static TSource Average(this TEnum items) + where TEnum : concrete, IEnumerator + where TSource : operator TSource / int + where TSource : operator TSource + TSource + { + return InternalAverage(items); + } + + static TSource InternalAverage(TEnum items) + where TEnum : concrete, IEnumerator + where TSource : operator TSource / int + where TSource : operator TSource + TSource + { + var count = 0; + TSource sum = ?; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + + switch (enumerator.GetNext()) + { + case .Ok(let val): + sum = val; + count++; + case .Err: return default; + } + + while (enumerator.GetNext() case .Ok(let val)) + { + sum += val; + count++; + } + + return sum / count; + } + } + + public static TSource Max(this TCollection items) + where TCollection : concrete, IEnumerable + where bool : operator TSource < TSource + { + return InternalMax(items.GetEnumerator()); + } + + public static TSource Max(this TEnum items) + where TEnum : concrete, IEnumerator + where bool : operator TSource < TSource + { + return InternalMax(items); + } + + static TSource InternalMax(TEnum items) + where TEnum : concrete, IEnumerator + where bool : operator TSource < TSource + { + TSource max = ?; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + switch (enumerator.GetNext()) + { + case .Ok(let val): max = val; + case .Err: return default; + } + + while (enumerator.GetNext() case .Ok(let val)) + { + let next = val; + if (max < next) + max = next; + } + } + return max; + } + + public static TSource Min(this TCollection items) + where TCollection : concrete, IEnumerable + where bool : operator TSource < TSource + { + return InternalMin(items.GetEnumerator()); + } + + public static TSource Min(this TEnum items) + where TEnum : concrete, IEnumerator + where bool : operator TSource < TSource + { + return InternalMin(items); + } + + static TSource InternalMin(TEnum items) + where TEnum : concrete, IEnumerator + where bool : operator TSource < TSource + { + TSource min = ?; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + switch (enumerator.GetNext()) + { + case .Ok(let val): min = val; + case .Err: return default; + } + + while (enumerator.GetNext() case .Ok(let val)) + { + let next = val; + if (next < min) + min = next; + } + } + return min; + } + + public static TSource Sum(this TCollection items) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + where TSource : operator TSource + TSource + { + return InternalSum(items.GetEnumerator()); + } + + public static TSource Sum(this TEnum items) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + where TSource : operator TSource + TSource + { + return InternalSum(items); + } + + static TSource InternalSum(TEnum items) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + where TSource : operator TSource + TSource + { + TSource sum = ?; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + switch (enumerator.GetNext()) + { + case .Ok(let val): sum = val; + case .Err: return default; + } + + while (enumerator.GetNext() case .Ok(let val)) + sum += val; + } + return sum; + } + + public static int Count(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (typeof(TCollection) == typeof(List)) + return (items as List).Count; + if (typeof(TCollection) == typeof(TSource[])) + return (items as TSource[]).Count; + + return InternalCount(items.GetEnumerator()); + } + + + public static int Count(this TEnum items) + where TEnum : concrete, IEnumerator + { + return InternalCount(items); + } + + public static int InternalCount(TEnum items) + where TEnum : concrete, IEnumerator + { + var count = 0; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + while (enumerator.GetNext() case .Ok) + count++; + } + return count; + } + + #endregion + + #region Find in enumerable + + internal static bool InternalElementAt(TEnum items, int index, out TSource val) + where TEnum : concrete, IEnumerator + { + var index; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + while (--index > 0) + { + if (enumerator.GetNext() case .Err) + break; + } + + if (index == 0 && enumerator.GetNext() case .Ok(out val)) + return true; + } + val = default; + return false; + } + + public static TSource ElementAt(this TCollection items, int index) + where TCollection : concrete, IEnumerable + { + if (InternalElementAt(items.GetEnumerator(), index, let val)) + return val; + + Runtime.FatalError("Not enough elements in the sequence."); + } + + public static TSource ElementAt(this TEnum items, int index) + where TEnum : concrete, IEnumerator + { + if (InternalElementAt(items, index, let val)) + return val; + + Runtime.FatalError("Not enough elements in the sequence."); + } + + public static TSource ElementAtOrDefault(this TCollection items, int index) + where TCollection : concrete, IEnumerable + { + if (InternalElementAt(items.GetEnumerator(), index, let val)) + return val; + + return default; + } + + public static TSource ElementAtOrDefault(this TEnum items, int index) + where TEnum : concrete, IEnumerator + { + if (InternalElementAt(items, index, let val)) + return val; + + return default; + } + + public static bool InternalFirst(TEnum items, out TSource val) + where TEnum : concrete, IEnumerator + { + val = default; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + if (enumerator.GetNext() case .Ok(out val)) + return true; + } + + return false; + } + + public static bool InternalFirst(TEnum items, out TSource val, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + val = default; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + if (enumerator.GetNext() case .Ok(out val)) + { + if (predicate(val)) + return true; + } + } + + return false; + } + + public static TSource First(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (InternalFirst(items.GetEnumerator(), let val)) + return val; + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource First(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + if (InternalFirst(items.GetEnumerator(), let val, predicate)) + return val; + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource First(this TEnum items) + where TEnum : concrete, IEnumerator + { + if (InternalFirst(items, let val)) + return val; + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource First(this TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + if (InternalFirst(items, let val, predicate)) + return val; + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource FirstOrDefault(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (InternalFirst(items.GetEnumerator(), let val)) + return val; + + return default; + } + + public static TSource FirstOrDefault(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + if (InternalFirst(items.GetEnumerator(), let val, predicate)) + return val; + + return default; + } + + /*public static WhereEnumerable + Where(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + return .(items.GetEnumerator(), predicate); + }*/ + + public static TSource FirstOrDefault(this TEnum items) + where TEnum : concrete, IEnumerator + { + if (InternalFirst(items, let val)) + return val; + + return default; + } + + internal static bool InternalLast(TEnum items, out TSource val) + where TEnum : concrete, IEnumerator + { + val = default; + var found = false; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + if (enumerator.GetNext() case .Ok(out val)) + found = true; + + while (enumerator.GetNext() case .Ok(let temp)) + val = temp; + } + + return found; + } + + public static TSource Last(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (InternalLast(items.GetEnumerator(), let val)) + return val; + + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource Last(this TEnum items) + where TEnum : concrete, IEnumerator + { + if (InternalLast(items, let val)) + return val; + + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource LastOrDefault(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (InternalLast(items.GetEnumerator(), let val)) + return val; + + return default; + } + + public static TSource LastOrDefault(this TEnum items) + where TEnum : concrete, IEnumerator + { + if (InternalLast(items, let val)) + return val; + + return default; + } + + internal static bool InternalSingle(TEnum items, out TSource val) + where TEnum : concrete, IEnumerator + { + val = default; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + + if (enumerator.GetNext() case .Ok(out val)) + { + if (enumerator.GetNext() case .Err) + return true; + + Runtime.FatalError("Sequence matched more than one element."); + } + } + + return false; + } + + public static TSource Single(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (InternalSingle(items.GetEnumerator(), let val)) + return val; + + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource Single(this TEnum items) + where TEnum : concrete, IEnumerator + { + if (InternalSingle(items, let val)) + return val; + + Runtime.FatalError("Sequence contained no elements."); + } + + public static TSource SingleOrDefault(this TCollection items) + where TCollection : concrete, IEnumerable + { + if (InternalSingle(items.GetEnumerator(), let val)) + return val; + + return default; + } + + public static TSource SingleOrDefault(this TEnum items) + where TEnum : concrete, IEnumerator + { + if (InternalSingle(items, let val)) + return val; + + return default; + } + + #endregion + +#region Enumerable Chains + struct Iterator + { + public static Iterator Wrap(TEnum items) + where TEnum : concrete, IEnumerator + { + return .(items); + } + + public static Iterator Wrap(TCollection items) + where TCollection : concrete, IEnumerable + { + return .(items.GetEnumerator()); + } + } + + struct Iterator : IDisposable + where TEnum : concrete, IEnumerator + { + internal TEnum mEnum; + + public this(TEnum items) + { + mEnum = items; + } + + [SkipCall] + public void Dispose() mut { } + + public static implicit operator Iterator(TEnum enumerator) => .(enumerator); + } + + extension Iterator : IDisposable where TEnum : IDisposable + { + public void Dispose() mut => mEnum.Dispose(); + } + + struct SelectEnumerable : Iterator, IEnumerable + where TSelect : delegate TResult(TSource) + where TEnum : concrete, IEnumerator + { + TSelect mDlg; + + public this(TEnum e, TSelect dlg) : base(e) + { + mDlg = dlg; + } + + Result GetNext() mut + { + if (mEnum.GetNext() case .Ok(let val)) + return mDlg(val); + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + } + } + + public static SelectEnumerable + Select(this TCollection items, TSelect select) + where TCollection : concrete, IEnumerable + where TSelect : delegate TResult(TSource) + { + return .(items.GetEnumerator(), select); + } + + public static SelectEnumerable + Select(this TEnum items, TSelect select) + where TEnum : concrete, IEnumerator + where TSelect : delegate TResult(TSource) + { + return .(items, select); + } + + struct WhereEnumerable : IEnumerable, IDisposable + where TPredicate : delegate bool(TSource) + where TEnum : concrete, IEnumerator + { + TPredicate mPredicate; + Iterator mIterator; + + public this(TEnum enumerator, TPredicate predicate) + { + mIterator = enumerator; + mPredicate = predicate; + } + + Result GetNext() mut + { + while (mIterator.mEnum.GetNext() case .Ok(let val)) + if (mPredicate(val)) + return .Ok(val); + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + public this(SelfOuter self) + { + mSelf = self; + } + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut + { + mSelf.Dispose(); + } + } + + public void Dispose() mut + { + mIterator.Dispose(); + } + } + + public static WhereEnumerable + Where(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + return .(items.GetEnumerator(), predicate); + } + + public static WhereEnumerable + Where(this TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + return .(items, predicate); + } + + struct TakeEnumerable : Iterator, IEnumerable + where TEnum : concrete, IEnumerator + { + int mCount; + + public this(TEnum enumerator, int count) : base(enumerator) + { + mCount = count; + } + + public Result GetNext() mut + { + while (mCount-- > 0 && mEnum.GetNext() case .Ok(let val)) + return val; + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + } + } + + public static TakeEnumerable + Take(this TCollection items, int count) + where TCollection : concrete, IEnumerable + { + return .(items.GetEnumerator(), count); + } + + public static TakeEnumerable + Take(this TEnum items, int count) + where TEnum : concrete, IEnumerator + { + return .(items, count); + } + + struct TakeWhileEnumerable : Iterator, IEnumerable + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + TPredicate mPredicate; + + public this(TEnum enumerator, TPredicate predicate) : base(enumerator) + { + mPredicate = predicate; + } + + Result GetNext() mut + { + if (mEnum.GetNext() case .Ok(let val)) + if (mPredicate(val)) + return .Ok(val); + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + } + } + + public static TakeWhileEnumerable + TakeWhile(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + return .(items.GetEnumerator(), predicate); + } + + public static TakeWhileEnumerable + TakeWhile(this TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + return .(items, predicate); + } + + struct SkipEnumerable : Iterator, IEnumerable + where TEnum : concrete, IEnumerator + { + int mCount; + + public this(TEnum enumerator, int count) : base(enumerator) + { + mCount = count; + } + + public Result GetNext() mut + { + while (mCount-- > 0 && mEnum.GetNext() case .Ok(?)) { } + + while (mEnum.GetNext() case .Ok(let val)) + return val; + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + } + } + + public static SkipEnumerable + Skip(this TCollection items, int count) + where TCollection : concrete, IEnumerable + { + return .(items.GetEnumerator(), count); + } + + public static SkipEnumerable + Skip(this TEnum items, int count) + where TEnum : concrete, IEnumerator + { + return .(items, count); + } + + struct SkipWhileEnumerable : Iterator, IEnumerable + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + TPredicate mPredicate; + int mState = 0; + + public this(TEnum enumerator, TPredicate predicate) : base(enumerator) + { + mPredicate = predicate; + } + + public Result GetNext() mut + { + switch (mState) { + case 0: + while (mEnum.GetNext() case .Ok(let val)) + { + if (!mPredicate(val)) + { + mState = 1; + return .Ok(val); + } + } + case 1: + return mEnum.GetNext(); + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + } + } + + public static SkipWhileEnumerable + SkipWhile(this TCollection items, TPredicate predicate) + where TCollection : concrete, IEnumerable + where TPredicate : delegate bool(TSource) + { + return .(items.GetEnumerator(), predicate); + } + + public static SkipWhileEnumerable + SkipWhile(this TEnum items, TPredicate predicate) + where TEnum : concrete, IEnumerator + where TPredicate : delegate bool(TSource) + { + return .(items, predicate); + } + + struct DefaultIfEmptyEnumerable : Iterator, IEnumerable + where TEnum : concrete, IEnumerator + { + TSource mDefaultValue; + int mState = 0; + + public this(TEnum enumerator, TSource defaultValue) : base(enumerator) + { + mDefaultValue = defaultValue; + } + + public Result GetNext() mut + { + switch (mState) { + case 0: + if (mEnum.GetNext() case .Ok(let val)) + { + mState = 1; + return .Ok(val); + } + + mState = 2; + return .Ok(mDefaultValue); + case 1: + return mEnum.GetNext(); + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + } + } + + public static DefaultIfEmptyEnumerable + DefaultIfEmpty(this TCollection items) + where TCollection : concrete, IEnumerable + { + return .(items.GetEnumerator(), default); + } + + public static DefaultIfEmptyEnumerable + DefaultIfEmpty(this TEnum items) + where TEnum : concrete, IEnumerator + { + return .(items, default); + } + + public static DefaultIfEmptyEnumerable + DefaultIfEmpty(this TCollection items, TSource defaultValue = default) + where TCollection : concrete, IEnumerable + { + return .(items.GetEnumerator(), defaultValue); + } + + public static DefaultIfEmptyEnumerable + DefaultIfEmpty(this TEnum items, TSource defaultValue = default) + where TEnum : concrete, IEnumerator + { + return .(items, defaultValue); + } + + struct DistinctEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TSource : IHashable + { + HashSet mDistinctValues; + HashSet.Enumerator mEnum; + Iterator mIterator; + int mState = 0; + + public this(TEnum enumerator) + { + mIterator = .(enumerator); + mDistinctValues = new .(); + mEnum = default; + } + + public Result GetNext() mut + { + switch (mState) { + case 0: + var enumerator = mIterator.mEnum; + while (enumerator.GetNext() case .Ok(let val)) + mDistinctValues.Add(val); + + mIterator.Dispose(); + mIterator = default; + mEnum = mDistinctValues.GetEnumerator(); + mState = 1; + fallthrough; + case 1: + return mEnum.GetNext(); + } + + return .Err; + } + + public void Dispose() mut + { + mEnum.Dispose(); + DeleteAndNullify!(mDistinctValues); + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + + public void Dispose() mut => mEnum.Dispose(); + } + } + + public static DistinctEnumerable + Distinct(this TCollection items) + where TCollection : concrete, IEnumerable + where TSource : IHashable + { + return .(items.GetEnumerator()); + } + + public static DistinctEnumerable + Distinct(this TEnum items) + where TEnum : concrete, IEnumerator + where TSource : IHashable + { + return .(items); + } + + struct ReverseEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + { + List mCopyValues; + List.Enumerator mEnum; + Iterator mIterator; + int mIndex = -1; + + public this(TEnum enumerator) + { + mIterator = .(enumerator); + mCopyValues = new .(); + mEnum = default; + } + + public Result GetNext() mut + { + switch (mIndex) { + case -1: + var enumerator = mIterator.mEnum; + while (enumerator.GetNext() case .Ok(let val)) + mCopyValues.Add(val); + + mIterator.Dispose(); + mIterator = default; + mEnum = mCopyValues.GetEnumerator(); + mIndex = mCopyValues.Count; + fallthrough; + default: + if (--mIndex >= 0) + return .Ok(mCopyValues[mIndex]); + + return .Err; + } + } + + public void Dispose() mut + { + mEnum.Dispose(); + DeleteAndNullify!(mCopyValues); + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + public this(SelfOuter self) + { + mSelf = self; + } + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut + { + mSelf.Dispose(); + } + } + } + + public static ReverseEnumerable + Reverse(this TCollection items) + where TCollection : concrete, IEnumerable + { + return .(items.GetEnumerator()); + } + + struct MapEnumerable : Iterator, IEnumerator, IEnumerable + where bool : operator TSource < TSource + where TSource : operator TSource - TSource + where TResult : operator TResult + TResult + where TResult : operator TResult - TResult + where float : operator float / TSource + where float : operator TSource * float + where float : operator float / TResult + where TResult : operator explicit float + where TEnum : concrete, IEnumerator + { + int mState = 0; + float mScale = 0f, mMapScale; + TSource mMin = default; + TResult mMapMin; + + public this(TEnum enumerator, TResult mapMin, TResult mapMax) : base(enumerator) + { + mMapMin = mapMin; + mMapScale = 1f / (mapMax - mapMin); + } + + public Result GetNext() mut + { + switch (mState) { + case 0: + var copyEnum = mEnum; + switch (copyEnum.GetNext()) { + case .Ok(let first): + var min = first; + var max = first; + + while (copyEnum.GetNext() case .Ok(let next)) + { + if (next < min) min = next; + if (max < next) max = next; + } + + mMin = min; + mScale = 1f / (max - min); + if (mScale == default) + { + mState = 2; + return .Ok(default); + } + + mState = 1; + case .Err: return .Err; + } + fallthrough; + case 1: + if (mEnum.GetNext() case .Ok(let val)) + return (TResult)(((val - mMin) * mScale) / mMapScale) + mMapMin; + case 2: + if (mEnum.GetNext() case .Ok(let val)) + return .Ok(default); + } + + return .Err; + } + + /*typealias SelfOuter = MapEnumerable; + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator: IEnumerator + { + SelfOuter mEnum; + public this(SelfOuter enumerator) + { + mEnum = enumerator; + } + public Result GetNext() mut => mEnum.GetNext(); + }*/ + + public Self GetEnumerator() => this; + } + + public static MapEnumerable + Map(this TCollection items, TResult min, TResult max) + where TCollection : concrete, IEnumerable + where bool : operator TSource < TSource + where TSource : operator TSource - TSource + where TResult : operator TResult + TResult + where TResult : operator TResult - TResult + where float : operator float / TSource + where float : operator TSource * float + where float : operator float / TResult + where TResult : operator explicit float + { + return .(items.GetEnumerator(), min, max); + } + + public static MapEnumerable + Map(this TEnum items, TResult min, TResult max) + where TEnum : concrete, IEnumerator + where bool : operator TSource < TSource + where TSource : operator TSource - TSource + where TResult : operator TResult + TResult + where TResult : operator TResult - TResult + where float : operator float / TSource + where float : operator TSource * float + where float : operator float / TResult + where TResult : operator explicit float + { + return .(items, min, max); + } +#endregion + +#region ToXYZ methods + public static void ToDictionary(this TCollection items, TKeyDlg keyDlg, TValueDlg valueDlg, Dictionary output) + where TCollection : concrete, IEnumerable + where TKey : IHashable + where TKeyDlg : delegate TKey(TSource) + where TValueDlg : delegate TValue(TSource) + { + for (var it in items) + output.Add(keyDlg(it), valueDlg(it)); + } + + public static void ToDictionary(this TEnum items, TKeyDlg keyDlg, TValueDlg valueDlg, Dictionary output) + where TEnum : concrete, IEnumerator + where TKey : IHashable + where TKeyDlg : delegate TKey(TSource) + where TValueDlg : delegate TValue(TSource) + { + for (var it in items) + output.Add(keyDlg(it), valueDlg(it)); + } + + public static void ToDictionary(this TCollection items, TKeyDlg keyDlg, Dictionary output) + where TCollection : concrete, IEnumerable + where TKey : IHashable + where TKeyDlg : delegate TKey(TSource) + { + for (var it in items) + output.Add(keyDlg(it), it); + } + + public static void ToDictionary(this TEnum items, TKeyDlg keyDlg, Dictionary output) + where TEnum : concrete, IEnumerator + where TKey : IHashable + where TKeyDlg : delegate TKey(TSource) + { + for (var it in items) + output.Add(keyDlg(it), it); + } + + public static void ToHashSet(this TCollection items, HashSet output) + where TCollection : concrete, IEnumerable + where TSource : IHashable + { + for (var it in items) + output.Add(it); + } + + public static void ToHashSet(this TEnum items, HashSet output) + where TEnum : concrete, IEnumerator + where TSource : IHashable + { + for (var it in items) + output.Add(it); + } + + public static void ToList(this TCollection items, List output) + where TCollection : concrete, IEnumerable + { + for (var it in items) + output.Add(it); + } + + public static void ToList(this TEnum items, List output) + where TEnum : concrete, IEnumerator + { + for (var it in items) + output.Add(it); + } +#endregion + +#region Aggregates + public static TSource + Aggregate(this TCollection items, TAccumulate accumulate) + where TCollection : concrete, IEnumerable + where TAccumulate : delegate TSource(TSource, TSource) + { + if (InternalAggregate(items.GetEnumerator(), accumulate, let result)) + return result; + + Runtime.FatalError("No elements in the sequence."); + } + + public static TSource + Aggregate(this TEnum items, TAccumulate accumulate) + where TEnum : concrete, IEnumerator + where TAccumulate : delegate TSource(TSource, TSource) + { + if (InternalAggregate(items, accumulate, let result)) + return result; + + Runtime.FatalError("No elements in the sequence."); + } + + public static TAccumulate + Aggregate(this TCollection items, TAccumulate seed, TAccDlg accumulate) + where TCollection : concrete, IEnumerable + where TAccDlg : delegate TAccumulate(TAccumulate, TSource) + { + if (InternalAggregate(items.GetEnumerator(), seed, accumulate, let result)) + return result; + + return seed; + } + + public static TAccumulate + Aggregate(this TEnum items, TAccumulate seed, TAccDlg accumulate) + where TEnum : concrete, IEnumerator + where TAccDlg : delegate TAccumulate(TAccumulate, TSource) + { + if (InternalAggregate(items, seed, accumulate, let result)) + return result; + + return seed; + } + + public static TResult + Aggregate(this TCollection items, TAccDlg accumulate, TResDlg resultSelector) + where TCollection : concrete, IEnumerable + where TAccDlg : delegate TSource(TSource, TSource) + where TResDlg : delegate TResult(TSource) + { + if (InternalAggregate(items.GetEnumerator(), accumulate, let result)) + return resultSelector(result); + + return resultSelector(default); + } + + public static TResult + Aggregate(this TEnum items, TAccDlg accumulate, TResDlg resultSelector) + where TEnum : concrete, IEnumerator + where TAccDlg : delegate TSource(TSource, TSource) + where TResDlg : delegate TResult(TSource) + { + if (InternalAggregate(items, accumulate, let result)) + return resultSelector(result); + + return resultSelector(default); + } + + public static TResult + Aggregate(this TCollection items, TAccumulate seed, TAccDlg accumulate, TResDlg resultSelector) + where TCollection : concrete, IEnumerable + where TAccDlg : delegate TAccumulate(TAccumulate, TSource) + where TResDlg : delegate TResult(TAccumulate) + { + if (InternalAggregate(items.GetEnumerator(), seed, accumulate, let result)) + return resultSelector(result); + + return resultSelector(seed); + } + + public static TResult + Aggregate(this TEnum items, TAccumulate seed, TAccDlg accumulate, TResDlg resultSelector) + where TEnum : concrete, IEnumerator + where TAccDlg : delegate TAccumulate(TAccumulate, TSource) + where TResDlg : delegate TResult(TAccumulate) + { + if (InternalAggregate(items, seed, accumulate, let result)) + return resultSelector(result); + + return resultSelector(seed); + } + + static bool InternalAggregate(TEnum items, TAccDlg accumulate, out TSource aggregate) + where TEnum : concrete, IEnumerator + where TAccDlg : delegate TSource(TSource, TSource) + { + aggregate = default; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + + //I guess we need at least 2 elements to do an accumulation without a seed? + if (!(enumerator.GetNext() case .Ok(out aggregate))) + return false; + + while (enumerator.GetNext() case .Ok(let val)) + aggregate = accumulate(aggregate, val); + } + return true; + } + + + static bool InternalAggregate(TEnum items, TAccumulate seed, TAccDlg func, out TAccumulate result) + where TEnum : concrete, IEnumerator + where TAccDlg : delegate TAccumulate(TAccumulate, TSource) + { + TAccumulate sum = seed; + var accumulated = false; + using (var iterator = Iterator.Wrap(items)) + { + var enumerator = iterator.mEnum; + + if (enumerator.GetNext() case .Ok(let val)) + { + sum = func(sum, val); + accumulated = true; + } + + if (accumulated) + while (enumerator.GetNext() case .Ok(let val)) + sum = func(sum, val); + } + + result = sum; + return accumulated; + } + #endregion + +#region GroupBy + + struct DynamicArray : IDisposable + { + TValue[] mPtr = default; + Span mSpan = default; + int mLength = 0; + int mSize = 4; + int mIndex = 0; + + public int Length => mLength; + + public this() + { + this.mPtr = new TValue[mSize]; + this.mLength = 0; + } + + public ref TValue this[int index] => ref mPtr[index]; + + public void Dispose() mut + { + DeleteAndNullify!(mPtr); + mPtr = null; + } + public void Add(TValue value) mut + { + if (mLength + 1 > mSize) + { + var newSize = mSize * 3 / 2; + var dst = new TValue[newSize]; + Array.Copy(mPtr, dst, mLength); + Swap!(mPtr, dst); + delete dst; + mSize = newSize; + } + mPtr[mLength++] = value; + } + + public Span.Enumerator GetEnumerator() mut + { + mSpan = .(mPtr, 0, mLength); + return mSpan.GetEnumerator(); + } + + public static implicit operator Span(Self it) => .(it.mPtr, 0, it.mLength); + } + + public extension DynamicArray : IDisposable + where TValue : IDisposable + { + public void Dispose() mut + { + for (var it in mPtr) + it.Dispose(); + + base.Dispose(); + } + } + + public struct Grouping : IEnumerable, IDisposable, IResettable + { + List mValues; + int mIndex = 0; + public readonly TKey Key; + + public this(TKey key) + { + Key = key; + mValues = new .(); + } + + public void Reset() mut + { + mIndex = 0; + } + + public List.Enumerator GetEnumerator() + { + return mValues.GetEnumerator(); + } + + public void Add(TValue value) mut + { + mValues.Add(value); + } + + public void Dispose() mut + { + DeleteAndNullify!(mValues); + } + } + + public class GroupByResult : IEnumerable> + where bool : operator TKey == TKey//where TKey : IHashable + { + DynamicArray> mResults = .() ~ mResults.Dispose(); + + public int Count => mResults.Length; + + public void Add(Grouping group) + { + mResults.Add(group); + } + + public ref Grouping this[int index] => ref mResults[index]; + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IRefEnumerator*>, IEnumerator>, IResettable + { + SelfOuter mSelf; + Span> mSpan; + int mIndex = 0; + + public this(SelfOuter self) + { + mSelf = self; + mSpan = self.mResults; + } + + public Result> GetNext() mut + { + if (mIndex < mSpan.Length) + return .Ok(mSpan[mIndex++]); + + return .Err; + } + + public Result*> GetNextRef() mut + { + if (mIndex < mSpan.Length) + return .Ok(&mSpan[mIndex++]); + + return .Err; + } + + public void Reset() mut + { + mIndex = 0; + } + } + } + + public struct GroupByEnumerable : IEnumerable>, IDisposable + where TEnum : concrete, IEnumerator + where bool : operator TKey == TKey//where TKey : IHashable + where TKeyDlg : delegate TKey(TSource) + where TValueDlg : delegate TValue(TSource) + { + GroupByResult mResults; + TKeyDlg mKeyDlg; + TValueDlg mValueDlg; + Iterator mIterator; + int mIndex = -1; + bool mDeleteResult; + + public this(GroupByResult results, TEnum enumerator, TKeyDlg keyDlg, TValueDlg valueDlg, bool deleteResult) + { + mResults = results; + mIterator = .(enumerator); + mKeyDlg = keyDlg; + mValueDlg = valueDlg; + mDeleteResult = deleteResult; + } + + Result> GetNext() mut + { + if (mIndex == -1) + { + while (mIterator.mEnum.GetNext() case .Ok(let val)) + { + let k = mKeyDlg(val); + let v = mValueDlg(val); + var added = false; + for (var it in ref mResults) + { + if (it.Key == k) + { + it.Add(v); + added = true; + } + } + + if (!added) + { + var group = mResults.Add(.. .(k)); + group.Add(v); + } + } + mIndex = 0; + } + + if (mIndex < mResults.Count) + return mResults[mIndex++]; + + return .Err; + } + + public void Dispose() mut + { + mIterator.Dispose(); + if (mDeleteResult) + DeleteAndNullify!(mResults); + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator>, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result> GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + } + + extension GroupByEnumerable + where TValueDlg : Object + { + public void Dispose() mut + { + base.Dispose(); + DeleteAndNullify!(mValueDlg); + } + } + + public static GroupByEnumerable + GroupBy(this TCollection items, TKeyDlg key) + where TCollection : concrete, IEnumerable + where TKeyDlg : delegate TKey(TSource) + where TKey : IHashable + { + //guess we could optimize out this scope with some code duplication + return .(new .(), items.GetEnumerator(), key, new (val) => val, true); + } + + public static GroupByEnumerable + GroupBy(this TEnum items, TKeyDlg key) + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where TKey : IHashable + { + //guess we could optimize out this scope with some code duplication + return .(new .(), items, key, new (val) => val, true); + } + + public static GroupByEnumerable + GroupBy(this TCollection items, TKeyDlg key, GroupByResult results) + where TCollection : concrete, IEnumerable + where TKeyDlg : delegate TKey(TSource) + where TKey : IHashable + { + //guess we could optimize out this scope with some code duplication + return .(results, items.GetEnumerator(), key, new (val) => val, false); + } + + public static GroupByEnumerable + GroupBy(this TEnum items, TKeyDlg key, GroupByResult results) + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where TKey : IHashable + { + //guess we could optimize out this scope with some code duplication + return .(results, items, key, new (val) => val, false); + } + + /*public static GroupByEnumerable GroupBy(this TCollection items, TKeyDlg + key, GroupByResult results) where TCollection : concrete, IEnumerable where TKeyDlg : + delegate TKey(TSource) where TKey : IHashable + { + //guess we could optimize out this scope with some code duplication + return .(results, items.GetEnumerator(), key, scope (val) => val); + }*/ +#endregion + struct UnionEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSource : IHashable + { + HashSet mDistinctValues; + Iterator mSource; + Iterator mOther; + int mState = 0; + + public this(TEnum sourceEnumerator, TEnum2 otherEnumerator) + { + mSource = sourceEnumerator; + mOther = otherEnumerator; + + mDistinctValues = new .(); + } + + Result GetNext() mut + { + switch (mState) { + case 0: + var e = mSource.mEnum; + while (e.GetNext() case .Ok(let val)) + if (mDistinctValues.Add(val)) + return .Ok(val); + + mState++; + fallthrough; + case 1: + var e = mOther.mEnum; + while (e.GetNext() case .Ok(let val)) + if (mDistinctValues.Add(val)) + return .Ok(val); + + mState++; + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + public void Dispose() mut + { + mSource.Dispose(); + mOther.Dispose(); + DeleteAndNullify!(mDistinctValues); + } + } + + public static UnionEnumerable + Union(this TCollection items, TCollection2 other) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + where TSource : IHashable + { + return .(items.GetEnumerator(), other.GetEnumerator()); + } + + public static UnionEnumerable + Union(this TEnum items, TCollection2 other) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + where TSource : IHashable + { + return .(items, other.GetEnumerator()); + } + + public static UnionEnumerable + Union(this TCollection items, TEnum other) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + where TSource : IHashable + { + return .(items.GetEnumerator(), other); + } + + public static UnionEnumerable + Union(this TEnum items, TEnum2 other) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSource : IHashable + { + return .(items, other); + } + + struct ExceptEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSource : IHashable + { + HashSet mDistinctValues; + Iterator mSource; + Iterator mOther; + int mState = 0; + + public this(TEnum sourceEnumerator, TEnum2 otherEnumerator) + { + mSource = sourceEnumerator; + mOther = otherEnumerator; + + mDistinctValues = new .(); + } + + Result GetNext() mut + { + switch (mState) { + case 0: + var e = mOther.mEnum; + while (e.GetNext() case .Ok(let val)) + mDistinctValues.Add(val); + + mState++; + fallthrough; + case 1: + var e = mSource.mEnum; + while (e.GetNext() case .Ok(let val)) + if (mDistinctValues.Add(val)) + return .Ok(val); + + mState++; + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + public void Dispose() mut + { + mSource.Dispose(); + mOther.Dispose(); + DeleteAndNullify!(mDistinctValues); + } + } + + public static ExceptEnumerable + Except(this TCollection items, TCollection2 other) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + where TSource : IHashable + { + return .(items.GetEnumerator(), other.GetEnumerator()); + } + + public static ExceptEnumerable + Except(this TEnum items, TCollection2 other) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + where TSource : IHashable + { + return .(items, other.GetEnumerator()); + } + + public static ExceptEnumerable + Except(this TCollection items, TEnum other) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + where TSource : IHashable + { + return .(items.GetEnumerator(), other); + } + + public static ExceptEnumerable + Except(this TEnum items, TEnum2 other) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSource : IHashable + { + return .(items, other); + } + + struct IntersectEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSource : IHashable + { + HashSet mDistinctValues; + Iterator mSource; + Iterator mIntersect; + int mState = 0; + + public this(TEnum sourceEnumerator, TEnum2 intersectEnumerator) + { + mSource = .(sourceEnumerator); + mIntersect = .(intersectEnumerator); + mDistinctValues = new .(); + } + + Result GetNext() mut + { + switch (mState) + { + case 0: + var e = mSource.mEnum; + while (e.GetNext() case .Ok(let val)) + mDistinctValues.Add(val); + + mState++; + fallthrough; + case 1: + var e = mIntersect.mEnum; + while (e.GetNext() case .Ok(let val)) + if (mDistinctValues.Remove(val)) + return .Ok(val); + + mState++; + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + public void Dispose() mut + { + mSource.Dispose(); + mIntersect.Dispose(); + DeleteAndNullify!(mDistinctValues); + } + } + + public static IntersectEnumerable + Intersect(this TCollection items, TCollection2 other) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + where TSource : IHashable + { + return .(items.GetEnumerator(), other.GetEnumerator()); + } + + public static IntersectEnumerable + Intersect(this TEnum items, TCollection2 other) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + where TSource : IHashable + { + return .(items, other.GetEnumerator()); + } + + public static IntersectEnumerable + Intersect(this TCollection items, TEnum other) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + where TSource : IHashable + { + return .(items.GetEnumerator(), other); + } + + public static IntersectEnumerable + Intersect(this TEnum items, TEnum2 other) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSource : IHashable + { + return .(items, other); + } + + struct ZipEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSelect : delegate TResult(TSource first, TSource second) + { + Iterator mSource; + Iterator mOther; + TSelect mSelect; + + public this(TEnum sourceEnumerator, TEnum2 otherEnumerator, TSelect select) + { + mSource = sourceEnumerator; + mOther = otherEnumerator; + mSelect = select; + } + + Result GetNext() mut + { + if (mSource.mEnum.GetNext() case .Ok(let first)) + if (mOther.mEnum.GetNext() case .Ok(let second)) + return mSelect(first, second); + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + + public void Dispose() mut + { + mSource.Dispose(); + mOther.Dispose(); + } + } + + public static ZipEnumerable + Zip(this TCollection items, TCollection2 other, TSelect select) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + where TSelect : delegate TResult(TSource first, TSource second) + { + return .(items.GetEnumerator(), other.GetEnumerator(), select); + } + + public static ZipEnumerable + Zip(this TEnum items, TCollection2 other, TSelect select) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + where TSelect : delegate TResult(TSource first, TSource second) + { + return .(items, other.GetEnumerator(), select); + } + + public static ZipEnumerable + Zip(this TCollection items, TEnum other, TSelect select) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + where TSelect : delegate TResult(TSource first, TSource second) + { + return .(items.GetEnumerator(), other, select); + } + + public static ZipEnumerable + Zip(this TEnum items, TEnum2 other, TSelect select) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSelect : delegate TResult(TSource first, TSource second) + { + return .(items, other, select); + } + + struct ConcatEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + { + Iterator mFirst; + Iterator mSecond; + int mState = 0; + + public this(TEnum firstEnumerator, TEnum2 secondEnumerator) + { + mFirst = firstEnumerator; + mSecond = secondEnumerator; + } + + Result GetNext() mut + { + switch (mState) { + case 0: + if (mFirst.mEnum.GetNext() case .Ok(let val)) + return .Ok(val); + + mState++; + fallthrough; + case 1: + if (mSecond.mEnum.GetNext() case .Ok(let val)) + return .Ok(val); + + mState++; + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + public void Dispose() mut + { + mFirst.Dispose(); + mSecond.Dispose(); + } + } + + + + public static ConcatEnumerable + Concat(this TCollection items, TCollection2 other) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + { + return .(items.GetEnumerator(), other.GetEnumerator()); + } + + public static ConcatEnumerable + Concat(this TEnum items, TCollection2 other) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + { + return .(items, other.GetEnumerator()); + } + + public static ConcatEnumerable + Concat(this TCollection items, TEnum other) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + { + return .(items.GetEnumerator(), other); + } + + public static ConcatEnumerable + Concat(this TEnum items, TEnum2 other) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + { + return .(items, other); + } + + public static ConcatEnumerable + Append(this TCollection items, TCollection2 other) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + { + return .(items.GetEnumerator(), other.GetEnumerator()); + } + + public static ConcatEnumerable + Append(this TEnum items, TCollection2 other) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + { + return .(items, other.GetEnumerator()); + } + + public static ConcatEnumerable + Append(this TCollection items, TEnum other) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + { + return .(items.GetEnumerator(), other); + } + + public static ConcatEnumerable + Append(this TEnum items, TEnum2 other) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + { + return .(items, other); + } + + public static ConcatEnumerable + Prepend(this TCollection items, TCollection2 other) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + { + return .(other.GetEnumerator(), items.GetEnumerator()); + } + + public static ConcatEnumerable + Prepend(this TEnum items, TCollection2 other) + where TEnum : concrete, IEnumerator + where TCollection2 : concrete, IEnumerable + { + return .(other.GetEnumerator(), items); + } + + public static ConcatEnumerable + Prepend(this TCollection items, TEnum other) + where TCollection : concrete, IEnumerable + where TEnum : concrete, IEnumerator + { + return .(other, items.GetEnumerator()); + } + + public static ConcatEnumerable + Prepend(this TEnum items, TEnum2 other) + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + { + return .(other, items); + } + + + + static class OrderByComparison + where int : operator T <=> T + { + typealias TCompare = delegate int(T lhs, T rhs); + public readonly static TCompare Comparison = (new (lhs, rhs) => lhs <=> rhs) ~ delete _; + } + + struct SortedEnumerable : IEnumerator<(TKey key, TSource value)>, IDisposable + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where TCompare : delegate int(TKey lhs, TKey rhs) + { + List<(TKey key, TSource value)> mOrderedList; + Iterator mIterator; + TKeyDlg mKey; + TCompare mCompare; + int mIndex; + int mCount = 0; + bool mDescending; + + public this(TEnum firstEnumerator, TKeyDlg key, TCompare compare, bool descending) + { + mOrderedList = new .(); + mKey = key; + mIterator = firstEnumerator; + mCompare = compare; + mIndex = -1; + mDescending = descending; + } + + public Result<(TKey key, TSource value)> GetNext() mut + { + if (mIndex == -1) + { + while (mIterator.mEnum.GetNext() case .Ok(let val)) + mOrderedList.Add((mKey(val), val)); + + mOrderedList.Sort(scope (l, r) => mCompare(l.key, r.key)); + mCount = mOrderedList.Count;//keeping vars local + mIndex = mDescending ? mCount : 0; + } + + if (mDescending) + { + if (mIndex > 0) + return .Ok(mOrderedList[--mIndex]); + } + else if (mIndex < mCount) + return .Ok(mOrderedList[mIndex++]); + + return .Err; + } + + + public Enumerator GetEnumerator() => .(this); + public struct Enumerator : IEnumerator<(TKey key, TSource value)>, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result<(TKey key, TSource value)> GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + + public void Dispose() mut + { + mIterator.Dispose(); + DeleteAndNullify!(mOrderedList); + } + } + + //if the data is a large struct, this could get pretty slow with all the copies + //we may need to investigate the ability to use ptrs instead if the type is a struct? + struct OrderByEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + { + typealias TCompare = delegate int(TSource lhs, TSource rhs); + + struct Comparer + { + public TCompare comparer; + public bool descending; + } + + DynamicArray mValues; + int[] mMap; + //List mOrderedList; + List mCompares = default; + Iterator mIterator; + int mIndex; + + public this(TEnum enumerator, TCompare compare, bool descending) + { + mCompares = new .(); + var add = mCompares.GrowUnitialized(1); + add.comparer = compare; + add.descending = descending; + + mValues = ?; + mMap = null; + mIterator = enumerator; + mIndex = -1; + } + + void QuickSort(int left, int right) { + var left; + var right; +#unwarn + repeat { + int i = left; + int j = right; + int x = mMap[i + ((j - i) >> 1)]; + repeat { + while (i < mMap.Count && CompareKeys(x, mMap[i]) > 0) i++; + while (j >= 0 && CompareKeys(x, mMap[j]) < 0) j--; + if (i > j) break; + if (i < j) { + int temp = mMap[i]; + mMap[i] = mMap[j]; + mMap[j] = temp; + } + i++; + j--; + } while (i <= j); + if (j - left <= right - i) { + if (left < j) QuickSort(left, j); + left = i; + } + else { + if (i < right) QuickSort(i, right); + right = j; + } + } while (left < right); + } + + [Inline] + int CompareKeys(int index1, int index2) + { + for(var cmp in ref mCompares) + { + int c = cmp.comparer(mValues[index1], mValues[index2]); + if (c != 0) + return cmp.descending ? -c : c; + } + + return index1 - index2; + } + + Result GetNext() mut + { + if (mIndex == -1) + { + mIndex = 0; + mValues = .(); + while (mIterator.mEnum.GetNext() case .Ok(let val)) + mValues.Add(val); + + let count = mValues.Length; + mMap = new int[count]; + for (int i = 0; i < count; i++) mMap[i] = i; + QuickSort( 0, count - 1); + } + + if (mIndex < mValues.Length) + return .Ok(mValues[mMap[mIndex++]]); + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + public void Dispose() mut + { + mIterator.Dispose(); + + for(var it in mCompares) + delete it.comparer; + DeleteAndNullify!(mCompares); + DeleteAndNullify!(mMap); + mValues.Dispose(); + } + + public Self ThenBy(TKeyDlg2 keySelect, TCompare2 comparison) mut + where TKeyDlg2 : delegate TKey2(TSource) + where TCompare2 : delegate int(TKey2 lhs, TKey2 rhs) + { + var add = mCompares.GrowUnitialized(1); + add.comparer = new (l, r) => comparison(keySelect(l), keySelect(r)); + add.descending = false; + return this; + } + + public Self ThenBy(TKeyDlg2 keySelect) + where TKeyDlg2 : delegate TKey2(TSource) + where int : operator TKey2 <=> TKey2 + { + let comparison = OrderByComparison.Comparison; + var add = mCompares.GrowUnitialized(1); + add.comparer = new (l, r) => comparison(keySelect(l), keySelect(r)); + add.descending = false; + return this; + } + + + public Self ThenByDescending(TKeyDlg2 keySelect, TCompare2 comparison) mut + where TKeyDlg2 : delegate TKey2(TSource) + where TCompare2 : delegate int(TKey2 lhs, TKey2 rhs) + { + var add = mCompares.GrowUnitialized(1); + add.comparer = new (l, r) => comparison(keySelect(l), keySelect(r)); + add.descending = true; + return this; + } + + public Self ThenByDescending(TKeyDlg2 keySelect) + where TKeyDlg2 : delegate TKey2(TSource) + where int : operator TKey2 <=> TKey2 + { + let comparison = OrderByComparison.Comparison; + var add = mCompares.GrowUnitialized(1); + add.comparer = new (l, r) => comparison(keySelect(l), keySelect(r)); + add.descending = true; + return this; + } + } + + public static OrderByEnumerable + OrderBy(this TCollection items, TKeyDlg keySelect) + where TCollection : concrete, IEnumerable + where TKeyDlg : delegate TKey(TSource) + where int : operator TKey <=> TKey + { + let comparison = OrderByComparison.Comparison; + return .(items.GetEnumerator(), new (l, r) => comparison(keySelect(l), keySelect(r)), false); + } + + public static OrderByEnumerable + OrderBy(this TEnum items, TKeyDlg keySelect) + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where int : operator TKey <=> TKey + { + let comparison = OrderByComparison.Comparison; + return .(items, new (l, r) => comparison(keySelect(l), keySelect(r)), false); + } + + public static OrderByEnumerable + OrderBy(this TCollection items, TKeyDlg keySelect, TCompare comparison) + where TCollection : concrete, IEnumerable + where TKeyDlg : delegate TKey(TSource) + where TCompare : delegate int(TKey lhs, TKey rhs) + { + return .(items.GetEnumerator(), new (l, r) => comparison(keySelect(l), keySelect(r)), false); + } + + public static OrderByEnumerable + OrderBy(this TEnum items, TKeyDlg keySelect, TCompare comparison) + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where TCompare : delegate int(TKey lhs, TKey rhs) + { + return .(items, new (l, r) => comparison(keySelect(l), keySelect(r)), false); + } + + public static OrderByEnumerable + OrderByDescending(this TCollection items, TKeyDlg keySelect) + where TCollection : concrete, IEnumerable + where TKeyDlg : delegate TKey(TSource) + where int : operator TKey <=> TKey + { + let comparison = OrderByComparison.Comparison; + return .(items.GetEnumerator(), new (l, r) => comparison(keySelect(l), keySelect(r)), true); + } + + public static OrderByEnumerable + OrderByDescending(this TEnum items, TKeyDlg keySelect) + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where int : operator TKey <=> TKey + { + let comparison = OrderByComparison.Comparison; + return .(items, new (l, r) => comparison(keySelect(l), keySelect(r)), true); + } + + public static OrderByEnumerable + OrderByDescending(this TCollection items, TKeyDlg keySelect, TCompare comparison) + where TCollection : concrete, IEnumerable + where TKeyDlg : delegate TKey(TSource) + where TCompare : delegate int(TKey lhs, TKey rhs) + { + return .(items.GetEnumerator(),new (l, r) => comparison(keySelect(l), keySelect(r)), true); + } + + public static OrderByEnumerable + OrderByDescending(this TEnum items, TKeyDlg keySelect, TCompare comparison) + where TEnum : concrete, IEnumerator + where TKeyDlg : delegate TKey(TSource) + where TCompare : delegate int(TKey lhs, TKey rhs) + { + return .(items, new (l, r) => comparison(keySelect(l), keySelect(r)), true); + } + + struct SelectManyEnumerable : IEnumerable, IDisposable + where TEnum : concrete, IEnumerator + where TEnum2 : concrete, IEnumerator + where TSelect : delegate TEnum2(TSource) + { + public readonly static String SelectManyEnum = new String() ~ delete _; + + Iterator mItems; + Iterator mCurrent = default; + TSelect mSelect; + int mState = -1; + + public this(TEnum firstEnumerator, TSelect select) + { + mItems = firstEnumerator; + mSelect = select; + } + + Result GetNext(out bool moveNext) mut + { + if (mState < 1) + { + if (mState == 0) + mCurrent.Dispose(); + + if (mItems.mEnum.GetNext() case .Ok(var val)) + { + mCurrent = mSelect(val); + mState = 1; + } + else + { + //no more elements + moveNext = false; + return .Err; + } + } + + if (mCurrent.mEnum.GetNext() case .Ok(let val)) + { + moveNext = false; + return .Ok(val); + } + + //done with current enumerator + mState = 0; + moveNext = true; + return .Err; + } + + Result GetNext() mut + { + var moveNext = true; + while (moveNext) + { + let result = GetNext(out moveNext); + if (!moveNext) + return result; + } + + return .Err; + } + + public Enumerator GetEnumerator() => .(this); + + public struct Enumerator : IEnumerator, IDisposable + { + SelfOuter mSelf; + + public this(SelfOuter self) + { + mSelf = self; + } + + public Result GetNext() mut => mSelf.GetNext(); + + public void Dispose() mut => mSelf.Dispose(); + } + + public void Dispose() mut + { + mItems.Dispose(); + } + } + extension SelectManyEnumerable + where TSelect : Object + { + public void Dispose() mut + { + base.Dispose(); + DeleteAndNullify!(mSelect); + } + } + + public static SelectManyEnumerable + SelectMany(this TCollection items, TSelect select) + where TCollection : concrete, IEnumerable + where TCollection2 : concrete, IEnumerable + where TSelect : delegate TCollection2(TSource) + { + return .(items.GetEnumerator(), new (x) => select(x).GetEnumerator()); + } + + public static SelectManyEnumerable + SelectMany(this TCollection items, TSelect select) + where TCollection : concrete, IEnumerable + where TEnum2 : concrete, IEnumerator + where TSelect : delegate TEnum2(TSource) + { + return .(items.GetEnumerator(), select); + } + + /*struct OfTypeEnumerable : Iterator, IEnumerator, IEnumerable + where TEnum : concrete, IEnumerator + where TSource : class + { + public this(TEnum enumerator) : base(enumerator) + { + } + + public Result GetNext() mut + { + while (mEnum.GetNext() case .Ok(let val)) + { + if (val is TOf) + return .Ok(*(TOf*)Internal.UnsafeCastToPtr(val)); + } + return .Err; + } + + public Self GetEnumerator() + { + return this; + } + } + + public static OfTypeEnumerable + OfType(this TCollection items) + where TCollection : concrete, IEnumerable + where TSource : class + { + return .(items.GetEnumerator()); + }*/ + + + } +} diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index c0d9a20..6b491e6 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -96,16 +96,19 @@ namespace GlitchyEngine.ImGui ImGui.GetIO().Fonts.Clear(); - String fullpath = scope String(); - Application.Get().ContentManager.GetFilePath(fullpath, settings.FontName); + // TODO: Fix fonts + //String fullpath = scope String(); + //Application.Get().ContentManager.GetFilePath(fullpath, settings.FontName); + /*Application.Get().ContentManager.GetStream(settings.FontName); if (File.Exists(fullpath)) { + ImGui.GetIO().Fonts.AddFontFromMemoryTTF(); ImGui.GetIO().Fonts.AddFontFromFileTTF(fullpath, settings.FontSize); } else - { + {*/ ImGui.GetIO().Fonts.AddFontDefault(); - } + //} #if GE_GRAPHICS_DX11 ImGuiImplDX11.CreateDeviceObjects(); diff --git a/GlitchyEngine/src/Renderer/SamplerState.bf b/GlitchyEngine/src/Renderer/SamplerState.bf index c52c570..ab23eef 100644 --- a/GlitchyEngine/src/Renderer/SamplerState.bf +++ b/GlitchyEngine/src/Renderer/SamplerState.bf @@ -2,12 +2,14 @@ using System; using GlitchyEngine.Core; using GlitchyEngine.Math; using System.Collections; +using Bon; namespace GlitchyEngine.Renderer { /** * Defines the filter function used when sampling from a texture. */ + [BonTarget, Reflect] public enum FilterFunction { /// Use point filtering (nearest neighbor) for sampling. @@ -17,7 +19,8 @@ namespace GlitchyEngine.Renderer /// Use anisotropic interpolation for sampling. Anisotropic } - + + [BonTarget, Reflect] public enum FilterMode { /// Just sample the texture. @@ -29,7 +32,8 @@ namespace GlitchyEngine.Renderer /// Return the maximum value of the fetched texels. Maximum } - + + [BonTarget, Reflect] public enum ComparisonFunction { /** @@ -65,7 +69,8 @@ namespace GlitchyEngine.Renderer */ Always = 8 } - + + [BonTarget, Reflect] public enum TextureAddressMode { /// Tile the texture at every (u,v) integer junction. @@ -92,7 +97,8 @@ namespace GlitchyEngine.Renderer */ MirrorOnce } - + + [BonTarget] public struct SamplerStateDescription : IHashable { public FilterFunction MinFilter = .Linear; diff --git a/GlitchyEngine/src/Renderer/Texture.bf b/GlitchyEngine/src/Renderer/Texture.bf index f3d6d9a..548b068 100644 --- a/GlitchyEngine/src/Renderer/Texture.bf +++ b/GlitchyEngine/src/Renderer/Texture.bf @@ -65,11 +65,17 @@ namespace GlitchyEngine.Renderer //public override extern uint32 ArraySize {get;} //public override extern uint32 MipLevels {get;} - public this(StringView path, bool pngSrgb = false) + private this(StringView path, bool pngSrgb = false) { _path = new String(path); LoadTexture(pngSrgb); } + + // TODO: remove + private this(Stream data) + { + LoadDds(data); + } const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; const String DdsMagicWord = "DDS "; @@ -78,7 +84,7 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileResourceFunction!(); - Stream data = Application.Get().ContentManager.GetFile(_path); + Stream data = Application.Get().ContentManager.GetStream(_path); defer delete data; var readResult = data.Read(); @@ -214,7 +220,7 @@ namespace GlitchyEngine.Renderer { Debug.Profiler.ProfileResourceFunction!(); - Stream data = Application.Get().ContentManager.GetFile(_path); + Stream data = Application.Get().ContentManager.GetStream(_path); defer delete data; LoadTexturePlatform(data); diff --git a/GlitchyEngine/src/World/SceneSerializer.bf b/GlitchyEngine/src/World/SceneSerializer.bf index 49780d1..2a5d86f 100644 --- a/GlitchyEngine/src/World/SceneSerializer.bf +++ b/GlitchyEngine/src/World/SceneSerializer.bf @@ -6,6 +6,8 @@ using System.IO; using GlitchyEngine.Math; using GlitchyEngine.Core; using System.Collections; +using GlitchyEngine.Renderer; +using GlitchyEngine.Content; namespace GlitchyEngine.World { @@ -84,11 +86,10 @@ namespace GlitchyEngine.World SerializeComponent(writer, entity, "SpriterRendererComponent", scope (component) => { - // TODO: Texture - Serialize.Value(writer, "Color", component.Color); - Serialize.Value(writer, "UvTransform", component.UvTransform); Serialize.Value(writer, "IsCircle", component.IsCircle); + Serialize.Value(writer, "Sprite", component.Sprite); + Serialize.Value(writer, "UvTransform", component.UvTransform); }); SerializeComponent(writer, entity, "TransformComponent", scope (component) => @@ -308,9 +309,19 @@ namespace GlitchyEngine.World Try!(Deserialize.Value(reader, "Color", out component.Color)); reader.EntryEnd(); - Try!(Deserialize.Value(reader, "UvTransform", out component.UvTransform)); - reader.EntryEnd(); Try!(Deserialize.Value(reader, "IsCircle", out component.IsCircle)); + reader.EntryEnd(); + + String spriteName; + Try!(Deserialize.Value(reader, "Sprite", out spriteName)); + + if (spriteName != null) + component.Sprite = Content.LoadAsset(spriteName); + + delete spriteName; + + reader.EntryEnd(); + Try!(Deserialize.Value(reader, "UvTransform", out component.UvTransform)); return .Ok; })); diff --git a/Sandbox/src/ExampleLayer.bf b/Sandbox/src/ExampleLayer.bf index 4311572..5df8d87 100644 --- a/Sandbox/src/ExampleLayer.bf +++ b/Sandbox/src/ExampleLayer.bf @@ -174,8 +174,8 @@ namespace Sandbox rsDesc.FrontCounterClockwise = false; _rasterizerStateClockWise = new RasterizerState(rsDesc); - _texture = new Texture2D("content/Textures/Checkerboard.dds"); - _ge_logo = new Texture2D("content/Textures/GE_Logo.dds"); + _texture = Content.LoadAsset("content/Textures/Checkerboard.dds");//new Texture2D("content/Textures/Checkerboard.dds"); + _ge_logo = Content.LoadAsset("content/Textures/GE_Logo.dds");//new Texture2D("content/Textures/GE_Logo.dds"); let sampler = SamplerStateManager.GetSampler( SamplerStateDescription() diff --git a/Sandbox/src/ExampleLayer2D.bf b/Sandbox/src/ExampleLayer2D.bf index d3ffb27..5e31229 100644 --- a/Sandbox/src/ExampleLayer2D.bf +++ b/Sandbox/src/ExampleLayer2D.bf @@ -96,7 +96,7 @@ namespace Sandbox _depthTarget = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height); - _checkerTexture = new Texture2D("content/Textures/Checkerboard.dds"); + _checkerTexture = Content.LoadAsset("content/Textures/Checkerboard.dds");//new Texture2D("content/Textures/Checkerboard.dds"); let sampler = SamplerStateManager.GetSampler( SamplerStateDescription() @@ -125,7 +125,7 @@ namespace Sandbox _depthStencilState = new DepthStencilState(dssDesc); - _spriteSheet = new Texture2D("content/Rpg/textures/spritesheet.png"); + _spriteSheet = Content.LoadAsset("content/Rpg/textures/spritesheet.png");//new Texture2D("content/Rpg/textures/spritesheet.png"); _spriteSheet.SamplerState = SamplerStateManager.PointClamp; _treeSprite = SubTexture2D.CreateFromGrid(_spriteSheet, Vector2(5, 10), Vector2(128), .(1, 2)); diff --git a/Sandbox/src/GammaTestLayer.bf b/Sandbox/src/GammaTestLayer.bf index 6b7e65b..efcbb66 100644 --- a/Sandbox/src/GammaTestLayer.bf +++ b/Sandbox/src/GammaTestLayer.bf @@ -43,11 +43,11 @@ namespace Sandbox _depthTarget = new DepthStencilTarget(_context.SwapChain.Width, _context.SwapChain.Height); - _checkerTexture = new Texture2D("content/Textures/Checkerboard.dds"); - _smallLines = new Texture2D("content/GammaTest/SmallLines.png"); - _gammaCorrectionBrightness = new Texture2D("content/GammaTest/gamma_correction_brightness.png"); + //_checkerTexture = new Texture2D("content/Textures/Checkerboard.dds"); + //_smallLines = new Texture2D("content/GammaTest/SmallLines.png"); + //_gammaCorrectionBrightness = new Texture2D("content/GammaTest/gamma_correction_brightness.png"); - _zeroPointFive = new Texture2D("content/GammaTest/zeroPointFive.png"); + //_zeroPointFive = new Texture2D("content/GammaTest/zeroPointFive.png"); let sampler = SamplerStateManager.GetSampler( SamplerStateDescription()