internal bool Load(bool wait, Func <Texture2D> load) { if (LoadImmediately) { Texture_Unsafe?.Dispose(); Texture_Unsafe = load(); FreeFastTextureLoad(); return(true); } // Let's queue a reload onto the main thread and call it a day. // Make sure to read the texture size immediately though! object queuedLoadLock; lock (queuedLoadLock = new object()) { _Texture_QueuedLoadLock = queuedLoadLock; Func <Texture2D> _load = load; load = () => { Texture2D tex; lock (queuedLoadLock) { if (_Texture_QueuedLoadLock != queuedLoadLock) { _load = null; FreeFastTextureLoad(); return(Texture_Unsafe); } // NOTE: If something dares to change texture info on the fly, GOOD LUCK. Texture_Unsafe?.Dispose(); Texture_Unsafe = tex = _load(); FreeFastTextureLoad(); _Texture_QueuedLoadLock = null; } if (_Texture_UnloadAfterReload) { tex?.Dispose(); tex = Texture_Unsafe; // ... can anything even swap the texture here? Texture_Unsafe = null; tex?.Dispose(); _Texture_UnloadAfterReload = false; } return(tex); }; _Texture_QueuedLoad = !_Texture_FTLLoading? MainThreadHelper.GetForceQueue(load) : MainThreadHelper.Get(load); } if (wait || _Texture_Requesting) { _Texture_QueuedLoad.GetResult(); } return(false); }
internal override unsafe void Reload() { // Unload task might end up conflicting with Reload - let's instead force-unload in Load. // Unload(); // Handle already queued loads appropriately. object queuedLoadLock = _Texture_QueuedLoadLock; if (queuedLoadLock != null && !_Texture_Reloading) { lock (queuedLoadLock) { // Queued task finished just in time. if (_Texture_QueuedLoadLock != queuedLoadLock) { return; } // If we still can, cancel the queued load, then proceed with lazy-loading. if (MainThreadHelper.IsMainThread) { _Texture_QueuedLoadLock = null; } } if (!MainThreadHelper.IsMainThread) { // Otherwise wait for it to get loaded, don't reload twice. (Don't wait locked!) while (!_Texture_QueuedLoad.IsValid) { Thread.Yield(); } _Texture_QueuedLoad.GetResult(); return; } } if (ftlEnabled && CanPreload && (Metadata?.StreamAsync ?? true) && !_Texture_Reloading && !_Texture_Requesting) { // Preload as we need to know the texture size WITHOUT ALLOCATING SPACE. // ... also because the texture size is required in the calling ctx past Reload. if (Preload(true)) { CountFastTextureLoad(); lock (queuedLoadLock = new object()) { _Texture_QueuedLoadLock = queuedLoadLock; _Texture_QueuedLoad = new MaybeAwaitable <Texture2D>(Task.Run(() => { try { lock (queuedLoadLock) { // Queued load cancelled or replaced with another queued load. if (_Texture_QueuedLoadLock != queuedLoadLock) { FreeFastTextureLoad(); return(Texture_Unsafe); } GrabFastTextureLoad(); // NOTE: If something dares to change texture info on the fly, GOOD LUCK. _Texture_Reloading = true; Reload(); if (_Texture_QueuedLoadLock == queuedLoadLock) { _Texture_QueuedLoadLock = null; } Texture2D tex = Texture_Unsafe; if (_Texture_UnloadAfterReload) { tex?.Dispose(); tex = Texture_Unsafe; // ... can anything even swap the texture here? Texture_Unsafe = null; tex?.Dispose(); _Texture_UnloadAfterReload = false; } return(tex); } } catch (Exception e) { Celeste.patch_Celeste.CriticalFailureHandler(e); throw; } }).GetAwaiter()); return; } } } _Texture_Reloading = false; if (Metadata != null) { if (Metadata.StreamAsync || MainThreadHelper.IsMainThread) { Stream stream = Metadata.Stream; if (stream != null) { using (stream) { bool premul = false; // Assume unpremultiplied by default. if (Metadata.TryGetMeta(out TextureMeta meta)) { premul = meta.Premultiplied; } if (ContentExtensions.TextureSetDataSupportsPtr) { int w, h; IntPtr dataPtr; if (premul) { ContentExtensions.LoadTextureRaw(Celeste.Celeste.Instance.GraphicsDevice, stream, out w, out h, out dataPtr); } else { ContentExtensions.LoadTextureLazyPremultiply(Celeste.Celeste.Instance.GraphicsDevice, stream, out w, out h, out dataPtr); } stream.Dispose(); Width = w; Height = h; Load(false, () => { Texture2D tex = new Texture2D(Celeste.Celeste.Instance.GraphicsDevice, w, h, false, SurfaceFormat.Color); tex.SetData(dataPtr); ContentExtensions.UnloadTextureRaw(dataPtr); return(tex); }); } else { int w, h; byte[] data; if (premul) { ContentExtensions.LoadTextureRaw(Celeste.Celeste.Instance.GraphicsDevice, stream, out w, out h, out data); } else { ContentExtensions.LoadTextureLazyPremultiply(Celeste.Celeste.Instance.GraphicsDevice, stream, out w, out h, out data); } stream.Dispose(); Width = w; Height = h; Load(false, () => { Texture2D tex = new Texture2D(Celeste.Celeste.Instance.GraphicsDevice, w, h, false, SurfaceFormat.Color); tex.SetData(data); data = null; return(tex); }); } } } else if (Fallback != null) { ((patch_VirtualTexture)(object)Fallback).Reload(); Texture_Unsafe = Fallback.Texture; } } else { // This is ugly but if the asset doesn't like multithreading, so be it. // Not even preloading will be beneficial here, and forget about GetMeta. Load(true, () => { using (Stream stream = Metadata.Stream) { if (stream != null) { bool premul = false; // Assume unpremultiplied by default. if (Metadata.TryGetMeta(out TextureMeta meta)) { premul = meta.Premultiplied; } if (premul) { Texture2D tex = Texture2D.FromStream(Celeste.Celeste.Instance.GraphicsDevice, stream); return(tex); } else if (ContentExtensions.TextureSetDataSupportsPtr) { ContentExtensions.LoadTextureLazyPremultiply(Celeste.Celeste.Instance.GraphicsDevice, stream, out int w, out int h, out IntPtr dataPtr); Texture2D tex = new Texture2D(Celeste.Celeste.Instance.GraphicsDevice, w, h, false, SurfaceFormat.Color); tex.SetData(dataPtr); ContentExtensions.UnloadTextureRaw(dataPtr); return(tex); } else { ContentExtensions.LoadTextureLazyPremultiply(Celeste.Celeste.Instance.GraphicsDevice, stream, out int w, out int h, out byte[] data); Texture2D tex = new Texture2D(Celeste.Celeste.Instance.GraphicsDevice, w, h, false, SurfaceFormat.Color); tex.SetData(data); data = null; return(tex); } } else if (Fallback != null) { ((patch_VirtualTexture)(object)Fallback).Reload(); return(Fallback.Texture); } return(null); }
public AsyncMaybeBuilder() { Task = new MaybeAwaitable <T>(new Maybe <T> .MaybeForStateBuilder <T>()); }
private bool ResetVertexBuffer() { // Checking for IsDisposed on other threads should be fine... if (Vertices != null && !Vertices.IsDisposed && !Vertices.GraphicsDevice.IsDisposed) { return(false); } // Handle already queued loads appropriately. object queuedLoadLock = _Vertices_QueuedLoadLock; if (queuedLoadLock != null) { lock (queuedLoadLock) { // Queued task finished just in time. if (_Vertices_QueuedLoadLock == null) { return(true); } // If we still can, cancel the queued load, then proceed with lazy-loading. if (MainThreadHelper.IsMainThread) { _Vertices_QueuedLoadLock = null; } } if (!MainThreadHelper.IsMainThread) { // Otherwise wait for it to get loaded, don't reload twice. (Don't wait locked!) while (!_Vertices_QueuedLoad.IsValid) { Thread.Yield(); } _Vertices_QueuedLoad.GetResult(); return(true); } } if (!(CoreModule.Settings.ThreadedGL ?? Everest.Flags.PreferThreadedGL) && !MainThreadHelper.IsMainThread && queuedLoadLock == null) { // Let's queue a reload onto the main thread and call it a day. lock (queuedLoadLock = new object()) { _Vertices_QueuedLoadLock = queuedLoadLock; _Vertices_QueuedLoad = MainThreadHelper.Get(() => { lock (queuedLoadLock) { if (_Vertices_QueuedLoadLock == null) { return(Vertices); } // Force-reload as we already returned true on the other thread. Vertices?.Dispose(); // NOTE: If something dares to change verts on the fly, make it wait on any existing tasks, then make it force-reload. Vertices = new VertexBuffer(Engine.Graphics.GraphicsDevice, typeof(VertexPositionTexture), verts.Length, BufferUsage.None); Vertices.SetData(verts); _Vertices_QueuedLoadLock = null; return(Vertices); } }); } return(true); } return(orig_ResetVertexBuffer()); }
public new void ResetBillboardBuffers() { // Checking for IsDisposed on other threads should be fine... if (billboardVertices != null && !billboardIndices.IsDisposed && !billboardIndices.GraphicsDevice.IsDisposed && billboardVertices != null && !billboardVertices.IsDisposed && !billboardVertices.GraphicsDevice.IsDisposed && billboardInfo.Length <= billboardVertices.VertexCount) { return; } // Handle already queued loads appropriately. object queuedLoadLock = _Billboard_QueuedLoadLock; if (queuedLoadLock != null) { lock (queuedLoadLock) { // Queued task finished just in time. if (_Billboard_QueuedLoadLock == null) { return; } // If we still can, cancel the queued load, then proceed with lazy-loading. if (MainThreadHelper.IsMainThread) { _Billboard_QueuedLoadLock = null; } } if (!MainThreadHelper.IsMainThread) { // Otherwise wait for it to get loaded, don't reload twice. (Don't wait locked!) while (!_Billboard_QueuedLoad.IsValid) { Thread.Yield(); } _Billboard_QueuedLoad.GetResult(); return; } } if (!(CoreModule.Settings.ThreadedGL ?? Everest.Flags.PreferThreadedGL) && !MainThreadHelper.IsMainThread && queuedLoadLock == null) { // Let's queue a reload onto the main thread and call it a day. lock (queuedLoadLock = new object()) { _Billboard_QueuedLoadLock = queuedLoadLock; _Billboard_QueuedLoad = MainThreadHelper.Get(() => { lock (queuedLoadLock) { if (_Billboard_QueuedLoadLock == null) { return(billboardVertices); } // Force-reload as we already returned true on the other thread. if (billboardVertices != null && !billboardVertices.IsDisposed) { billboardVertices.Dispose(); } if (billboardIndices != null && !billboardIndices.IsDisposed) { billboardIndices.Dispose(); } // NOTE: If something dares to change verts on the fly, make it wait on any existing tasks, then make it force-reload. // Let's rely on the original code for now. orig_ResetBillboardBuffers(); _Billboard_QueuedLoadLock = null; return(billboardVertices); } }); } return; } orig_ResetBillboardBuffers(); }