1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-26 06:28:35 +01:00

Compare commits

..

2 commits

Author SHA1 Message Date
Ell
e5cfebef3b cleaned up element addition/removal code 2022-11-24 19:46:20 +01:00
Ell
e21729de67 fixed some memory management issues in MLEM.Ui 2022-11-24 18:38:51 +01:00
6 changed files with 64 additions and 41 deletions

View file

@ -68,6 +68,10 @@ Fixes
- Fixed an exception when trying to force-update the area of an element without a ui system - Fixed an exception when trying to force-update the area of an element without a ui system
- Fixed the scroll bar of an empty panel being positioned incorrectly - Fixed the scroll bar of an empty panel being positioned incorrectly
- Fixed UiControls maintaining old input states when input types are toggled off - Fixed UiControls maintaining old input states when input types are toggled off
- Fixed an occasional deadlock when a game is disposed with a scrolling Panel present
Removals
- Marked Element.OnDisposed as obsolete in favor of the more predictable OnRemovedFromUi
### MLEM.Data ### MLEM.Data
Additions Additions

View file

@ -31,7 +31,7 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
public UiSystem System { public UiSystem System {
get => this.system; get => this.system;
internal set { private set {
this.system = value; this.system = value;
this.Controls = value?.Controls; this.Controls = value?.Controls;
this.Style = this.Style.OrStyle(value?.Style); this.Style = this.Style.OrStyle(value?.Style);
@ -50,7 +50,7 @@ namespace MLEM.Ui.Elements {
/// This element's <see cref="RootElement"/>. /// This element's <see cref="RootElement"/>.
/// Note that this value is set even if this element has a <see cref="Parent"/>. To get the element that represents the root element, use <see cref="RootElement.Element"/>. /// Note that this value is set even if this element has a <see cref="Parent"/>. To get the element that represents the root element, use <see cref="RootElement.Element"/>.
/// </summary> /// </summary>
public RootElement Root { get; internal set; } public RootElement Root { get; private set; }
/// <summary> /// <summary>
/// The scale that this ui element renders with /// The scale that this ui element renders with
/// </summary> /// </summary>
@ -425,9 +425,10 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
public GenericCallback OnRemovedFromUi; public GenericCallback OnRemovedFromUi;
/// <summary> /// <summary>
/// Event that is called when this element's <see cref="Dispose"/> method is called, which also happens in <see cref="Finalize"/>. /// Event that is called when this element's <see cref="Dispose"/> method is called.
/// This event is useful for unregistering global event handlers when this object should be destroyed. /// This event is useful for unregistering global event handlers when this object should be destroyed.
/// </summary> /// </summary>
[Obsolete("OnDisposed will be removed in a future update. To unregister custom event handlers, use OnRemovedFromUi instead.")]
public GenericCallback OnDisposed; public GenericCallback OnDisposed;
/// <summary> /// <summary>
@ -480,14 +481,15 @@ namespace MLEM.Ui.Elements {
this.size = size; this.size = size;
this.Children = new ReadOnlyCollection<Element>(this.children); this.Children = new ReadOnlyCollection<Element>(this.children);
this.GetTabNextElement += (backward, next) => next; this.GetTabNextElement = (backward, next) => next;
this.GetGamepadNextElement += (dir, next) => next; this.GetGamepadNextElement = (dir, next) => next;
this.SetAreaDirty(); this.SetAreaDirty();
this.SetSortedChildrenDirty(); this.SetSortedChildrenDirty();
} }
/// <inheritdoc /> /// <inheritdoc />
[Obsolete("Dispose will be removed in a future update. To unregister custom event handlers, use OnRemovedFromUi instead.")]
~Element() { ~Element() {
this.Dispose(); this.Dispose();
} }
@ -504,12 +506,7 @@ namespace MLEM.Ui.Elements {
index = this.children.Count; index = this.children.Count;
this.children.Insert(index, element); this.children.Insert(index, element);
element.Parent = this; element.Parent = this;
element.AndChildren(e => { element.AndChildren(e => e.AddedToUi(this.System, this.Root));
e.Root = this.Root;
e.System = this.System;
e.OnAddedToUi?.Invoke(e);
this.Root?.InvokeOnElementAdded(e);
});
this.OnChildAdded?.Invoke(this, element); this.OnChildAdded?.Invoke(this, element);
this.SetSortedChildrenDirty(); this.SetSortedChildrenDirty();
element.SetAreaDirty(); element.SetAreaDirty();
@ -528,12 +525,7 @@ namespace MLEM.Ui.Elements {
// upwards to us if the element is auto-positioned // upwards to us if the element is auto-positioned
element.SetAreaDirty(); element.SetAreaDirty();
element.Parent = null; element.Parent = null;
element.AndChildren(e => { element.AndChildren(e => e.RemovedFromUi());
e.Root = null;
e.System = null;
e.OnRemovedFromUi?.Invoke(e);
this.Root?.InvokeOnElementRemoved(e);
});
this.OnChildRemoved?.Invoke(this, element); this.OnChildRemoved?.Invoke(this, element);
this.SetSortedChildrenDirty(); this.SetSortedChildrenDirty();
} }
@ -1098,6 +1090,7 @@ namespace MLEM.Ui.Elements {
} }
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
[Obsolete("Dispose will be removed in a future update. To unregister custom event handlers, use OnRemovedFromUi instead.")]
public virtual void Dispose() { public virtual void Dispose() {
this.OnDisposed?.Invoke(this); this.OnDisposed?.Invoke(this);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
@ -1187,6 +1180,31 @@ namespace MLEM.Ui.Elements {
return this.TransformInverse(position); return this.TransformInverse(position);
} }
/// <summary>
/// Called when this element is added to a <see cref="UiSystem"/> and, optionally, a given <see cref="RootElement"/>.
/// This method is called in <see cref="AddChild{T}"/> and <see cref="UiSystem.Add"/>.
/// </summary>
/// <param name="system">The ui system to add to.</param>
/// <param name="root">The root element to add to.</param>
protected internal virtual void AddedToUi(UiSystem system, RootElement root) {
this.Root = root;
this.System = system;
this.OnAddedToUi?.Invoke(this);
root?.InvokeOnElementAdded(this);
}
/// <summary>
/// Called when this element is removed from a <see cref="UiSystem"/> and <see cref="RootElement"/>.
/// This method is called in <see cref="RemoveChild"/> and <see cref="UiSystem.Remove"/>.
/// </summary>
protected internal virtual void RemovedFromUi() {
var root = this.Root;
this.Root = null;
this.System = null;
this.OnRemovedFromUi?.Invoke(this);
root?.InvokeOnElementRemoved(this);
}
/// <summary> /// <summary>
/// A delegate used for the <see cref="Element.OnTextInput"/> event. /// A delegate used for the <see cref="Element.OnTextInput"/> event.
/// </summary> /// </summary>

View file

@ -177,15 +177,6 @@ namespace MLEM.Ui.Elements {
return base.GetElementUnderPos(position); return base.GetElementUnderPos(position);
} }
/// <inheritdoc />
public override void Dispose() {
if (this.renderTarget != null) {
this.renderTarget.Dispose();
this.renderTarget = null;
}
base.Dispose();
}
/// <summary> /// <summary>
/// Scrolls this panel's <see cref="ScrollBar"/> to the given <see cref="Element"/> in such a way that its center is positioned in the center of this panel. /// Scrolls this panel's <see cref="ScrollBar"/> to the given <see cref="Element"/> in such a way that its center is positioned in the center of this panel.
/// </summary> /// </summary>
@ -236,6 +227,15 @@ namespace MLEM.Ui.Elements {
this.ScrollChildren(); this.ScrollChildren();
} }
/// <inheritdoc />
protected internal override void RemovedFromUi() {
base.RemovedFromUi();
// we dispose our render target when removing so that it doesn't cause a memory leak
// if we're added back afterwards, it'll be recreated in ScrollSetup anyway
this.renderTarget?.Dispose();
this.renderTarget = null;
}
/// <summary> /// <summary>
/// Prepares the panel for auto-scrolling, creating the render target and setting up the scroll bar's maximum value. /// Prepares the panel for auto-scrolling, creating the render target and setting up the scroll bar's maximum value.
/// </summary> /// </summary>
@ -274,11 +274,13 @@ namespace MLEM.Ui.Elements {
// update the render target // update the render target
var targetArea = (Rectangle) this.GetRenderTargetArea(); var targetArea = (Rectangle) this.GetRenderTargetArea();
if (targetArea.Width <= 0 || targetArea.Height <= 0) if (targetArea.Width <= 0 || targetArea.Height <= 0) {
this.renderTarget?.Dispose();
this.renderTarget = null;
return; return;
}
if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) { if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) {
if (this.renderTarget != null) this.renderTarget?.Dispose();
this.renderTarget.Dispose();
this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height); this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height);
this.relevantChildrenDirty = true; this.relevantChildrenDirty = true;
} }

View file

@ -27,7 +27,7 @@ namespace MLEM.Ui.Parsers {
public static readonly ElementType[] ElementTypes = public static readonly ElementType[] ElementTypes =
#if NET6_0_OR_GREATER #if NET6_0_OR_GREATER
Enum.GetValues<ElementType>(); Enum.GetValues<ElementType>();
#else #else
(ElementType[]) Enum.GetValues(typeof(ElementType)); (ElementType[]) Enum.GetValues(typeof(ElementType));
#endif #endif
@ -145,9 +145,15 @@ namespace MLEM.Ui.Parsers {
throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images"); throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images");
TextureRegion image = null; TextureRegion image = null;
LoadImageAsync();
return new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) { return new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) {
OnDisposed = e => image?.Texture.Dispose() OnAddedToUi = e => {
if (image == null)
LoadImageAsync();
},
OnRemovedFromUi = e => {
image?.Texture.Dispose();
image = null;
}
}; };
async void LoadImageAsync() { async void LoadImageAsync() {

View file

@ -345,10 +345,7 @@ namespace MLEM.Ui {
var root = new RootElement(name, element, this); var root = new RootElement(name, element, this);
this.rootElements.Add(root); this.rootElements.Add(root);
root.Element.AndChildren(e => { root.Element.AndChildren(e => {
e.Root = root; e.AddedToUi(this, root);
e.System = this;
e.OnAddedToUi?.Invoke(e);
root.InvokeOnElementAdded(e);
e.SetAreaDirty(); e.SetAreaDirty();
}); });
this.OnRootAdded?.Invoke(root); this.OnRootAdded?.Invoke(root);
@ -368,10 +365,7 @@ namespace MLEM.Ui {
this.rootElements.Remove(root); this.rootElements.Remove(root);
this.Controls.SelectElement(root, null); this.Controls.SelectElement(root, null);
root.Element.AndChildren(e => { root.Element.AndChildren(e => {
e.Root = null; e.RemovedFromUi();
e.System = null;
e.OnRemovedFromUi?.Invoke(e);
root.InvokeOnElementRemoved(e);
e.SetAreaDirty(); e.SetAreaDirty();
}); });
this.OnRootRemoved?.Invoke(root); this.OnRootRemoved?.Invoke(root);

View file

@ -411,7 +411,6 @@ namespace MLEM.Graphics {
this.indices?.Dispose(); this.indices?.Dispose();
foreach (var buffer in this.vertexBuffers) foreach (var buffer in this.vertexBuffers)
buffer.Dispose(); buffer.Dispose();
GC.SuppressFinalize(this);
} }
private Item Add(Texture2D texture, Vector2 pos, Vector2 offset, Vector2 size, float sin, float cos, Color color, Vector2 texTl, Vector2 texBr, float depth) { private Item Add(Texture2D texture, Vector2 pos, Vector2 offset, Vector2 size, float sin, float cos, Color color, Vector2 texTl, Vector2 texBr, float depth) {