From 3faaf826885b264b3d303077882e595eb928a2b6 Mon Sep 17 00:00:00 2001 From: Elliot Sime <5482039+eksime@users.noreply.github.com> Date: Thu, 25 May 2023 23:41:01 +0100 Subject: [PATCH] thread safety changes for tweener class and small cleanup --- Blish HUD/Library/Glide/MemberAccessor.cs | 43 +- Blish HUD/Library/Glide/MemberLerper.cs | 41 +- Blish HUD/Library/Glide/Tween.cs | 538 ++++++++-------- Blish HUD/Library/Glide/Tweener.cs | 708 ++++++++++------------ 4 files changed, 611 insertions(+), 719 deletions(-) diff --git a/Blish HUD/Library/Glide/MemberAccessor.cs b/Blish HUD/Library/Glide/MemberAccessor.cs index 6800e93ed..5063472e2 100644 --- a/Blish HUD/Library/Glide/MemberAccessor.cs +++ b/Blish HUD/Library/Glide/MemberAccessor.cs @@ -1,30 +1,26 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; +namespace Glide { + using System; + using System.Linq.Expressions; + using System.Reflection; -namespace Glide -{ - internal class MemberAccessor - { - public object Target { get; private set; } + internal class MemberAccessor { public string MemberName { get; private set; } public Type MemberType { get; private set; } - public object Value - { - get { return getMethod(this.Target); } - set { setMethod(this.Target, value); } + public void SetValue(object target, object value) { + setMethod(target, value); } - public MemberAccessor(object target, string name, bool writeRequired = true) - { + public object GetValue(object target) { + return getMethod(target); + } + + public MemberAccessor(object target, string name, bool writeRequired = true) { var T = target.GetType(); PropertyInfo propInfo = null; FieldInfo fieldInfo = null; - this.Target = target; - if ((propInfo = T.GetProperty(name, flags)) != null) - { + if ((propInfo = T.GetProperty(name, flags)) != null) { this.MemberType = propInfo.PropertyType; this.MemberName = propInfo.Name; @@ -35,8 +31,7 @@ public MemberAccessor(object target, string name, bool writeRequired = true) getMethod = Expression.Lambda>(convert, param).Compile(); } - if (writeRequired) - { + if (writeRequired) { var param = Expression.Parameter(typeof(object)); var argument = Expression.Parameter(typeof(object)); var setterCall = Expression.Call( @@ -46,9 +41,7 @@ public MemberAccessor(object target, string name, bool writeRequired = true) setMethod = Expression.Lambda>(setterCall, param, argument).Compile(); } - } - else if ((fieldInfo = T.GetField(name, flags)) != null) - { + } else if ((fieldInfo = T.GetField(name, flags)) != null) { this.MemberType = fieldInfo.FieldType; this.MemberName = fieldInfo.Name; @@ -69,15 +62,13 @@ public MemberAccessor(object target, string name, bool writeRequired = true) setMethod = Expression.Lambda>(assignExp, self, value).Compile(); } - } - else - { + } else { throw new Exception(string.Format("Field or {0} property '{1}' not found on object of type {2}.", writeRequired ? "read/write" : "readable", name, T.FullName)); } } - + protected Func getMethod; protected Action setMethod; private static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; diff --git a/Blish HUD/Library/Glide/MemberLerper.cs b/Blish HUD/Library/Glide/MemberLerper.cs index b99afcef7..38fa78d39 100644 --- a/Blish HUD/Library/Glide/MemberLerper.cs +++ b/Blish HUD/Library/Glide/MemberLerper.cs @@ -1,24 +1,21 @@ -using System; +namespace Glide { + using System; -namespace Glide -{ - public abstract class MemberLerper - { - [Flags] - public enum Behavior - { - None = 0, - Reflect = 1, - Rotation = 2, - RotationRadians = 4, - RotationDegrees = 8, - Round = 16 - } - - protected const float DEG = 180f / (float) Math.PI; - protected const float RAD = (float) Math.PI / 180f; - - public abstract void Initialize(Object fromValue, Object toValue, Behavior behavior); - public abstract object Interpolate(float t, object currentValue, Behavior behavior); - } + public abstract class MemberLerper { + [Flags] + public enum Behavior { + None = 0, + Reflect = 1, + Rotation = 2, + RotationRadians = 4, + RotationDegrees = 8, + Round = 16 + } + + protected const float DEG = 180f / (float)Math.PI; + protected const float RAD = (float)Math.PI / 180f; + + public abstract void Initialize(Object fromValue, Object toValue, Behavior behavior); + public abstract object Interpolate(float t, object currentValue, Behavior behavior); + } } diff --git a/Blish HUD/Library/Glide/Tween.cs b/Blish HUD/Library/Glide/Tween.cs index 7e81cea6d..43f122cd4 100644 --- a/Blish HUD/Library/Glide/Tween.cs +++ b/Blish HUD/Library/Glide/Tween.cs @@ -1,88 +1,80 @@ -using System; -using System.Collections.Generic; - -namespace Glide -{ - public partial class Tween - { - [Flags] - public enum RotationUnit - { - Degrees, - Radians - } - -#region Callbacks - private Func ease; +namespace Glide { + using System; + using System.Collections.Generic; + + public partial class Tween { + [Flags] + public enum RotationUnit { + Degrees, + Radians + } + + #region Callbacks + private Func ease; private Action begin, update, complete, repeat; -#endregion + #endregion -#region Timing - public bool Paused { get; private set; } + #region Timing + public bool Paused { get; private set; } private float Delay, repeatDelay; private float Duration; private float Time, time; -#endregion - - private bool initialized, running; + #endregion + + private bool initialized, running; private int repeatCount; private MemberLerper.Behavior behavior; - + private List vars; private List lerpers; private List start, end; private Dictionary varHash; - private TweenerImpl Parent; private IRemoveTweens Remover; - - /// - /// The time remaining before the tween ends or repeats. - /// + /// + /// The time remaining before the tween ends or repeats. + /// public float TimeRemaining { get { return Duration - Time; } } - + /// /// A value between 0 and 1, where 0 means the tween has not been started and 1 means that it has completed. /// public float Completion { get { return Duration > 0 ? Math.Min(Math.Max(Time / Duration, 0), 1) : 1; } } - + /// /// Whether the tween is currently looping. /// public bool Looping { get { return repeatCount != 0; } } - + /// /// The object this tween targets. Will be null if the tween represents a timer. /// - public object Target { get; private set; } - - private Tween(object target, float duration, float delay, TweenerImpl parent) - { - this.Target = target; + public object Target { get; } + + private Tween(object target, float duration, float delay, IRemoveTweens remover) { + Target = target; Duration = duration; Delay = delay; - Parent = parent; - Remover = parent; - - varHash = new Dictionary(); - vars = new List(); - lerpers = new List(); - start = new List(); - end = new List(); - behavior = MemberLerper.Behavior.None; - } - - private void AddLerp(MemberLerper lerper, MemberAccessor info, object from, object to) - { - varHash.Add(info.MemberName, vars.Count); - vars.Add(info); - - start.Add(from); - end.Add(to); - - lerpers.Add(lerper); - } - + Remover = remover; + + varHash = new Dictionary(); + vars = new List(); + lerpers = new List(); + start = new List(); + end = new List(); + behavior = MemberLerper.Behavior.None; + } + + private void AddLerp(MemberLerper lerper, MemberAccessor info, object from, object to) { + varHash.Add(info.MemberName, vars.Count); + vars.Add(info); + + start.Add(from); + end.Add(to); + + lerpers.Add(lerper); + } + private void Update(float elapsed) { int i = 0; bool doReverse = false; @@ -147,9 +139,11 @@ private void Update(float elapsed) { } i = vars.Count; - while (i-- > 0) { - if (vars[i] != null) { - vars[i].Value = lerperSet[i].Interpolate(t, vars[i].Value, behavior); + if (this.Target is object target) { + while (i-- > 0) { + if (vars[i] != null) { + vars[i].SetValue(target, lerperSet[i].Interpolate(t, vars[i].GetValue(target), behavior)); + } } } @@ -166,235 +160,217 @@ private void Update(float elapsed) { complete(); } } - -#region Behavior - /// - /// Apply target values to a starting point before tweening. - /// - /// The values to apply, in an anonymous type ( new { prop1 = 100, prop2 = 0} ). - /// A reference to this. - public Tween From(object values) - { - var props = values.GetType().GetProperties(); - for (int i = 0; i < props.Length; ++i) - { - var property = props[i]; - var propValue = property.GetValue(values, null); - - int index = -1; - if (varHash.TryGetValue(property.Name, out index)) - { - // if we're already tweening this value, adjust the range - start[index] = propValue; - } - - // if we aren't tweening this value, just set it - var info = new MemberAccessor(this.Target, property.Name, true); - info.Value = propValue; - } - - return this; - } - - /// - /// Set the easing function. - /// - /// The Easer to use. - /// A reference to this. - public Tween Ease(Func ease) - { - this.ease = ease; - return this; - } - - /// - /// Set a function to call when the tween begins (useful when using delays). Can be called multiple times for compound callbacks. - /// - /// The function that will be called when the tween starts, after the delay. - /// A reference to this. - public Tween OnBegin(Action callback) - { - if (begin == null) begin = callback; - else begin += callback; - return this; - } - - /// - /// Set a function to call when the tween finishes. Can be called multiple times for compound callbacks. - /// If the tween repeats infinitely, this will be called each time; otherwise it will only run when the tween is finished repeating. - /// - /// The function that will be called on tween completion. - /// A reference to this. - public Tween OnComplete(Action callback) - { - if (complete == null) complete = callback; - else complete += callback; - return this; - } + + #region Behavior + /// + /// Apply target values to a starting point before tweening. + /// + /// The values to apply, in an anonymous type ( new { prop1 = 100, prop2 = 0} ). + /// A reference to this. + public Tween From(object values) { + + if (this.Target is object target) { + var props = values.GetType().GetProperties(); + for (int i = 0; i < props.Length; ++i) { + var property = props[i]; + var propValue = property.GetValue(values, null); + + int index = -1; + if (varHash.TryGetValue(property.Name, out index)) { + // if we're already tweening this value, adjust the range + start[index] = propValue; + } + + // if we aren't tweening this value, just set it + var info = new MemberAccessor(target, property.Name, true); + info.SetValue(target, propValue); + } + } + + return this; + } + + /// + /// Set the easing function. + /// + /// The Easer to use. + /// A reference to this. + public Tween Ease(Func ease) { + this.ease = ease; + return this; + } + + /// + /// Set a function to call when the tween begins (useful when using delays). Can be called multiple times for compound callbacks. + /// + /// The function that will be called when the tween starts, after the delay. + /// A reference to this. + public Tween OnBegin(Action callback) { + if (begin == null) begin = callback; + else begin += callback; + return this; + } + + /// + /// Set a function to call when the tween finishes. Can be called multiple times for compound callbacks. + /// If the tween repeats infinitely, this will be called each time; otherwise it will only run when the tween is finished repeating. + /// + /// The function that will be called on tween completion. + /// A reference to this. + public Tween OnComplete(Action callback) { + if (complete == null) complete = callback; + else complete += callback; + return this; + } public Tween OnRepeat(Action callback) { if (repeat == null) repeat = callback; else repeat += callback; return this; } - - /// - /// Set a function to call as the tween updates. Can be called multiple times for compound callbacks. - /// - /// The function to use. - /// A reference to this. - public Tween OnUpdate(Action callback) - { - if (update == null) update = callback; - else update += callback; - return this; - } - - /// - /// Enable repeating. - /// - /// Number of times to repeat. Leave blank or pass a negative number to repeat infinitely. - /// A reference to this. - public Tween Repeat(int times = -1) - { - repeatCount = times; - return this; - } - - /// - /// Set a delay for when the tween repeats. - /// - /// How long to wait before repeating. - /// A reference to this. - public Tween RepeatDelay(float delay) - { - repeatDelay = delay; - return this; - } - - /// - /// Sets the tween to reverse every other time it repeats. Repeating must be enabled for this to have any effect. - /// - /// A reference to this. - public Tween Reflect() - { - behavior |= MemberLerper.Behavior.Reflect; - return this; - } - - /// - /// Swaps the start and end values of the tween. - /// - /// A reference to this. - public Tween Reverse() - { - int i = vars.Count; - while (i --> 0) - { - var s = start[i]; - var e = end[i]; - - // Set start to end and end to start - start[i] = e; - end[i] = s; - - lerpers[i].Initialize(e, s, behavior); - } - - return this; - } - - /// - /// Whether this tween handles rotation. - /// - /// A reference to this. - public Tween Rotation(RotationUnit unit = RotationUnit.Degrees) - { - behavior |= MemberLerper.Behavior.Rotation; - behavior |= (unit == RotationUnit.Degrees) ? MemberLerper.Behavior.RotationDegrees : MemberLerper.Behavior.RotationRadians; - - return this; - } - - /// - /// Whether tweened values should be rounded to integer values. - /// - /// A reference to this. - public Tween Round() - { - behavior |= MemberLerper.Behavior.Round; - return this; - } -#endregion - -#region Control - /// - /// Cancel tweening given properties. - /// - /// - public void Cancel(params string[] properties) - { - var canceled = 0; - for (int i = 0; i < properties.Length; ++i) - { - var index = 0; - if (!varHash.TryGetValue(properties[i], out index)) - continue; - - varHash.Remove(properties[i]); - vars[index] = null; - lerpers[index] = null; - start[index] = null; - end[index] = null; - - canceled++; - } - - if (canceled == vars.Count) - Cancel(); - } - - /// - /// Remove tweens from the tweener without calling their complete functions. - /// - public void Cancel() - { + + /// + /// Set a function to call as the tween updates. Can be called multiple times for compound callbacks. + /// + /// The function to use. + /// A reference to this. + public Tween OnUpdate(Action callback) { + if (update == null) update = callback; + else update += callback; + return this; + } + + /// + /// Enable repeating. + /// + /// Number of times to repeat. Leave blank or pass a negative number to repeat infinitely. + /// A reference to this. + public Tween Repeat(int times = -1) { + repeatCount = times; + return this; + } + + /// + /// Set a delay for when the tween repeats. + /// + /// How long to wait before repeating. + /// A reference to this. + public Tween RepeatDelay(float delay) { + repeatDelay = delay; + return this; + } + + /// + /// Sets the tween to reverse every other time it repeats. Repeating must be enabled for this to have any effect. + /// + /// A reference to this. + public Tween Reflect() { + behavior |= MemberLerper.Behavior.Reflect; + return this; + } + + /// + /// Swaps the start and end values of the tween. + /// + /// A reference to this. + public Tween Reverse() { + int i = vars.Count; + while (i-- > 0) { + var s = start[i]; + var e = end[i]; + + // Set start to end and end to start + start[i] = e; + end[i] = s; + + lerpers[i].Initialize(e, s, behavior); + } + + return this; + } + + /// + /// Whether this tween handles rotation. + /// + /// A reference to this. + public Tween Rotation(RotationUnit unit = RotationUnit.Degrees) { + behavior |= MemberLerper.Behavior.Rotation; + behavior |= (unit == RotationUnit.Degrees) ? MemberLerper.Behavior.RotationDegrees : MemberLerper.Behavior.RotationRadians; + + return this; + } + + /// + /// Whether tweened values should be rounded to integer values. + /// + /// A reference to this. + public Tween Round() { + behavior |= MemberLerper.Behavior.Round; + return this; + } + #endregion + + #region Control + /// + /// Cancel tweening given properties. + /// + /// + public void Cancel(params string[] properties) { + var canceled = 0; + for (int i = 0; i < properties.Length; ++i) { + var index = 0; + if (!varHash.TryGetValue(properties[i], out index)) + continue; + + varHash.Remove(properties[i]); + vars[index] = null; + lerpers[index] = null; + start[index] = null; + end[index] = null; + + canceled++; + } + + if (canceled == vars.Count) + Cancel(); + } + + /// + /// Remove tweens from the tweener without calling their complete functions. + /// + public void Cancel() { Remover.Remove(this); - } - - /// - /// Assign tweens their final value and remove them from the tweener. - /// - public void CancelAndComplete() - { - time = Time = Duration; - update = null; + } + + /// + /// Assign tweens their final value and remove them from the tweener. + /// + public void CancelAndComplete() { + time = Time = Duration; + update = null; Remover.Remove(this); - } - - /// - /// Set tweens to pause. They won't update and their delays won't tick down. - /// - public void Pause() - { + } + + /// + /// Set tweens to pause. They won't update and their delays won't tick down. + /// + public void Pause() { this.Paused = true; - } - - /// - /// Toggle tweens' paused value. - /// - public void PauseToggle() - { + } + + /// + /// Toggle tweens' paused value. + /// + public void PauseToggle() { this.Paused = !this.Paused; - } - - /// - /// Resumes tweens from a paused state. - /// - public void Resume() - { + } + + /// + /// Resumes tweens from a paused state. + /// + public void Resume() { this.Paused = false; - } -#endregion - } + } + #endregion + } } \ No newline at end of file diff --git a/Blish HUD/Library/Glide/Tweener.cs b/Blish HUD/Library/Glide/Tweener.cs index 03f60eeca..87d5e5587 100644 --- a/Blish HUD/Library/Glide/Tweener.cs +++ b/Blish HUD/Library/Glide/Tweener.cs @@ -1,393 +1,321 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Glide -{ - public class Tweener : Tween.TweenerImpl {}; - - public partial class Tween - { - private interface IRemoveTweens // lol get it - { - void Remove(Tween t); - } - - public class TweenerImpl : IRemoveTweens - { - static TweenerImpl() - { - registeredLerpers = new Dictionary(); - var numericTypes = new Type[] { - typeof(Int16), - typeof(Int32), - typeof(Int64), - typeof(UInt16), - typeof(UInt32), - typeof(UInt64), - typeof(Single), - typeof(Double), +namespace Glide { + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + public class Tweener : Tween.TweenerImpl { }; + + public partial class Tween { + private interface IRemoveTweens // lol get it + { + void Remove(Tween t); + } + + public class TweenerImpl : IRemoveTweens { + static TweenerImpl() { + registeredLerpers = new Dictionary(); + var numericTypes = new Type[] { + typeof(Int16), + typeof(Int32), + typeof(Int64), + typeof(UInt16), + typeof(UInt32), + typeof(UInt64), + typeof(Single), + typeof(Double), typeof(byte) - }; - - for (int i = 0; i < numericTypes.Length; i++) - SetLerper(numericTypes[i]); - } - - /// - /// Associate a Lerper class with a property type. - /// - /// The Lerper class to use for properties of the given type. - /// The type of the property to associate the given Lerper with. - public static void SetLerper(Type propertyType) where TLerper : MemberLerper, new() - { - SetLerper(typeof(TLerper), propertyType); - } - - /// - /// Associate a Lerper type with a property type. - /// - /// The type of the Lerper to use for properties of the given type. - /// The type of the property to associate the given Lerper with. - public static void SetLerper(Type lerperType, Type propertyType) - { - registeredLerpers[propertyType] = lerperType.GetConstructor(Type.EmptyTypes); - } - - protected TweenerImpl() - { - tweens = new Dictionary>(); - toRemove = new List(); - toAdd = new List(); - allTweens = new List(); - } - - private static Dictionary registeredLerpers; - private Dictionary> tweens; - private List toRemove, toAdd, allTweens; - - /// - /// Tweens a set of properties on an object. - /// To tween instance properties/fields, pass the object. - /// To tween static properties/fields, pass the type of the object, using typeof(ObjectType) or object.GetType(). - /// - /// The object or type to tween. - /// The values to tween to, in an anonymous type ( new { prop1 = 100, prop2 = 0} ). - /// Duration of the tween in seconds. - /// Delay before the tween starts, in seconds. - /// Whether pre-existing tweens should be overwritten if this tween involves the same properties. - /// The tween created, for setting properties on. - public Tween Tween(T target, object values, float duration, float delay = 0, bool overwrite = true) where T : class - { - if (target == null) - throw new ArgumentNullException("target"); - - // Prevent tweening on structs if you cheat by casting target as Object - var targetType = target.GetType(); - if (targetType.IsValueType) - throw new Exception("Target of tween cannot be a struct!"); - - var tween = new Tween(target, duration, delay, this); - toAdd.Add(tween); - - if (values == null) // valid in case of manual timer - return tween; - - var props = values.GetType().GetProperties(); - for (int i = 0; i < props.Length; ++i) - { - List library = null; - if (overwrite && tweens.TryGetValue(target, out library)) - { - for (int j = 0; j < library.Count; j++) - library[j].Cancel(props[i].Name); - } - - var property = props[i]; - var info = new MemberAccessor(target, property.Name); - var to = new MemberAccessor(values, property.Name, false); - var lerper = CreateLerper(info.MemberType); - - tween.AddLerp(lerper, info, info.Value, to.Value); - } - - AddAndRemove(); - return tween; - } - - /// - /// Starts a simple timer for setting up callback scheduling. - /// - /// How long the timer will run for, in seconds. - /// How long to wait before starting the timer, in seconds. - /// The tween created, for setting properties. - public Tween Timer(float duration, float delay = 0) - { - var tween = new Tween(null, duration, delay, this); - AddAndRemove(); - toAdd.Add(tween); - return tween; - } - - /// - /// Remove tweens from the tweener without calling their complete functions. - /// - public void Cancel() - { - toRemove.AddRange(allTweens); - } - - /// - /// Assign tweens their final value and remove them from the tweener. - /// - public void CancelAndComplete() - { - for (int i = 0; i < allTweens.Count; ++i) - allTweens[i].CancelAndComplete(); - } - - /// - /// Set tweens to pause. They won't update and their delays won't tick down. - /// - public void Pause() - { - for (int i = 0; i < allTweens.Count; ++i) - { - var tween = allTweens[i]; - tween.Pause(); - } - } - - /// - /// Toggle tweens' paused value. - /// - public void PauseToggle() - { - for (int i = 0; i < allTweens.Count; ++i) - { - var tween = allTweens[i]; - tween.PauseToggle(); - } - } - - /// - /// Resumes tweens from a paused state. - /// - public void Resume() - { - for (int i = 0; i < allTweens.Count; ++i) - { - var tween = allTweens[i]; - tween.Resume(); - } - } - - /// - /// Updates the tweener and all objects it contains. - /// - /// Seconds elapsed since last update. - public void Update(float secondsElapsed) { - var tweens = allTweens.ToArray(); - - for (int i = 0; i < tweens.Length; ++i) { - tweens[i]?.Update(secondsElapsed); + }; + + for (int i = 0; i < numericTypes.Length; i++) { + SetLerper(numericTypes[i]); + } + } + + /// + /// Associate a Lerper class with a property type. + /// + /// The Lerper class to use for properties of the given type. + /// The type of the property to associate the given Lerper with. + public static void SetLerper(Type propertyType) where TLerper : MemberLerper, new() { + SetLerper(typeof(TLerper), propertyType); + } + + /// + /// Associate a Lerper type with a property type. + /// + /// The type of the Lerper to use for properties of the given type. + /// The type of the property to associate the given Lerper with. + public static void SetLerper(Type lerperType, Type propertyType) { + registeredLerpers[propertyType] = lerperType.GetConstructor(Type.EmptyTypes); + } + + protected TweenerImpl() { + tweens = new ConcurrentDictionary>(); + toRemove = new ConcurrentQueue(); + toAdd = new ConcurrentQueue(); + } + + + private static Dictionary registeredLerpers; + + private ConcurrentDictionary> tweens; + private ConcurrentQueue toRemove; + private ConcurrentQueue toAdd; + + /// + /// Tweens a set of properties on an object. + /// To tween instance properties/fields, pass the object. + /// To tween static properties/fields, pass the type of the object, using typeof(ObjectType) or object.GetType(). + /// + /// The object or type to tween. + /// The values to tween to, in an anonymous type ( new { prop1 = 100, prop2 = 0} ). + /// Duration of the tween in seconds. + /// Delay before the tween starts, in seconds. + /// Whether pre-existing tweens should be overwritten if this tween involves the same properties. + /// The tween created, for setting properties on. + public Tween Tween(T target, object values, float duration, float delay = 0, bool overwrite = true) where T : class { + if (target == null) { + throw new ArgumentNullException("target"); + } + + // Prevent tweening on structs if you cheat by casting target as Object + var targetType = typeof(T); + if (targetType.IsValueType) { + throw new Exception("Target of tween cannot be a struct!"); + } + + var tween = new Tween(target, duration, delay, this); + toAdd.Enqueue(tween); + + if (values == null) // valid in case of manual timer + return tween; + + var props = values.GetType().GetProperties(); + for (int i = 0; i < props.Length; ++i) { + if (overwrite) { + ForAllTweens(target, tw => tw.Cancel(props[i].Name)); + } + + var property = props[i]; + var info = new MemberAccessor(target, property.Name); + var to = new MemberAccessor(values, property.Name, false); + var lerper = CreateLerper(info.MemberType); + + tween.AddLerp(lerper, info, info.GetValue(target), to.GetValue(values)); + } + + AddAndRemove(); + return tween; + } + + /// + /// Starts a simple timer for setting up callback scheduling. + /// + /// How long the timer will run for, in seconds. + /// How long to wait before starting the timer, in seconds. + /// The tween created, for setting properties. + public Tween Timer(float duration, float delay = 0) { + var tween = new Tween(this, duration, delay, this); + toAdd.Enqueue(tween); + AddAndRemove(); + return tween; + } + + /// + /// Remove tweens from the tweener without calling their complete functions. + /// + public void Cancel() { + ForAllTweens(toRemove.Enqueue); + } + + /// + /// Assign tweens their final value and remove them from the tweener. + /// + public void CancelAndComplete() { + ForAllTweens(tw => tw.CancelAndComplete()); + } + + /// + /// Set tweens to pause. They won't update and their delays won't tick down. + /// + public void Pause() { + ForAllTweens(tw => tw.Pause()); + } + + /// + /// Toggle tweens' paused value. + /// + public void PauseToggle() { + ForAllTweens(tw => tw.PauseToggle()); + } + + /// + /// Resumes tweens from a paused state. + /// + public void Resume() { + ForAllTweens(tw => tw.Resume()); + } + + /// + /// Updates the tweener and all objects it contains. + /// + /// Seconds elapsed since last update. + public void Update(float secondsElapsed) { + ForAllTweens(tw => tw.Update(secondsElapsed)); + + AddAndRemove(); + } + + private void ForAllTweens(Action action) { + foreach (object target in this.tweens.Keys) { + ForAllTweens(target, action); + } + } + + private void ForAllTweens(object target, Action action) { + if (tweens.TryGetValue(target, out var list)) { + lock (list) { + foreach (Tween tween in list) { + action(tween); + } + } + } + } + + private MemberLerper CreateLerper(Type propertyType) { + if (!registeredLerpers.TryGetValue(propertyType, out ConstructorInfo lerper)) { + throw new Exception(string.Format("No Lerper found for type {0}.", propertyType.FullName)); + } + + return (MemberLerper)lerper.Invoke(null); + } + + void IRemoveTweens.Remove(Tween tween) { + toRemove.Enqueue(tween); + } + + private void AddAndRemove() { + while (toAdd.TryDequeue(out Tween tween)) { + List list = tweens.GetOrAdd(tween.Target, _ => new List(4)); + lock (list) { + list.Add(tween); + } + } + + while (toRemove.TryDequeue(out Tween tween)) { + if (tweens.TryGetValue(tween.Target, out List list)) { + lock (list) { + list.Remove(tween); + } + + if (!list.Any()) { + tweens.TryRemove(tween.Target, out _); + } + } + } + } + + #region Target control + /// + /// Cancel all tweens with the given target. + /// + /// The object being tweened that you want to cancel. + public void TargetCancel(object target) { + ForAllTweens(target, tw => tw.Cancel()); + } + + /// + /// Cancel tweening named properties on the given target. + /// + /// The object being tweened that you want to cancel properties on. + /// The properties to cancel. + public void TargetCancel(object target, params string[] properties) { + ForAllTweens(target, tw => tw.Cancel(properties)); + } + + /// + /// Cancel, complete, and call complete callbacks for all tweens with the given target.. + /// + /// The object being tweened that you want to cancel and complete. + public void TargetCancelAndComplete(object target) { + ForAllTweens(target, tw => tw.CancelAndComplete()); + } + + + /// + /// Pause all tweens with the given target. + /// + /// The object being tweened that you want to pause. + public void TargetPause(object target) { + ForAllTweens(target, tw => tw.Pause()); + } + + /// + /// Toggle the pause state of all tweens with the given target. + /// + /// The object being tweened that you want to toggle pause. + public void TargetPauseToggle(object target) { + ForAllTweens(target, tw => tw.PauseToggle()); + } + + /// + /// Resume all tweens with the given target. + /// + /// The object being tweened that you want to resume. + public void TargetResume(object target) { + ForAllTweens(target, tw => tw.Resume()); + } + #endregion + + private class NumericLerper : MemberLerper { + float from, to, range; + + public override void Initialize(object fromValue, object toValue, Behavior behavior) { + from = Convert.ToSingle(fromValue); + to = Convert.ToSingle(toValue); + range = to - from; + + if ((behavior & Behavior.Rotation) == Behavior.Rotation) { + float angle = from; + if ((behavior & Behavior.RotationRadians) == Behavior.RotationRadians) + angle *= DEG; + + if (angle < 0) + angle = 360 + angle; + + float r = angle + range; + float d = r - angle; + float a = (float)Math.Abs(d); + + if (a >= 180) range = (360 - a) * (d > 0 ? -1 : 1); + else range = d; + } + } + + public override object Interpolate(float t, object current, Behavior behavior) { + var value = from + range * t; + if ((behavior & Behavior.Rotation) == Behavior.Rotation) { + if ((behavior & Behavior.RotationRadians) == Behavior.RotationRadians) + value *= DEG; + + value %= 360.0f; + + if (value < 0) + value += 360.0f; + + if ((behavior & Behavior.RotationRadians) == Behavior.RotationRadians) + value *= RAD; + } + + if ((behavior & Behavior.Round) == Behavior.Round) + value = (float)Math.Round(value); + + var type = current.GetType(); + return Convert.ChangeType(value, type); } - - AddAndRemove(); - } - - private MemberLerper CreateLerper(Type propertyType) - { - ConstructorInfo lerper = null; - if (!registeredLerpers.TryGetValue(propertyType, out lerper)) - throw new Exception(string.Format("No Lerper found for type {0}.", propertyType.FullName)); - - return (MemberLerper) lerper.Invoke(null); - } - - void IRemoveTweens.Remove(Tween tween) - { - toRemove.Add(tween); - } - - private void AddAndRemove() - { - for (int i = 0; i < toAdd.Count; ++i) - { - var tween = toAdd[i]; - allTweens.Add(tween); - if (tween.Target == null) continue; // don't sort timers by target - - List list = null; - if (!tweens.TryGetValue(tween.Target, out list)) - tweens[tween.Target] = list = new List(); - - list.Add(tween); - } - - for (int i = 0; i < toRemove.Count; ++i) - { - var tween = toRemove[i]; - allTweens.Remove(tween); - if (tween.Target == null) continue; // see above - - List list = null; - if (tweens.TryGetValue(tween.Target, out list)) - { - list.Remove(tween); - if (list.Count == 0) - { - tweens.Remove(tween.Target); - } - } - - allTweens.Remove(tween); - } - - toAdd.Clear(); - toRemove.Clear(); - } - - #region Target control - /// - /// Cancel all tweens with the given target. - /// - /// The object being tweened that you want to cancel. - public void TargetCancel(object target) - { - List list; - if (tweens.TryGetValue(target, out list)) - { - for (int i = 0; i < list.Count; ++i) - list[i]?.Cancel(); - } - } - - /// - /// Cancel tweening named properties on the given target. - /// - /// The object being tweened that you want to cancel properties on. - /// The properties to cancel. - public void TargetCancel(object target, params string[] properties) - { - List list; - if (tweens.TryGetValue(target, out list)) - { - for (int i = 0; i < list.Count; ++i) - list[i]?.Cancel(properties); - } - } - - /// - /// Cancel, complete, and call complete callbacks for all tweens with the given target.. - /// - /// The object being tweened that you want to cancel and complete. - public void TargetCancelAndComplete(object target) - { - List list; - if (tweens.TryGetValue(target, out list)) - { - for (int i = 0; i < list.Count; ++i) - list[i]?.CancelAndComplete(); - } - } - - - /// - /// Pause all tweens with the given target. - /// - /// The object being tweened that you want to pause. - public void TargetPause(object target) - { - List list; - if (tweens.TryGetValue(target, out list)) - { - for (int i = 0; i < list.Count; ++i) - list[i]?.Pause(); - } - } - - /// - /// Toggle the pause state of all tweens with the given target. - /// - /// The object being tweened that you want to toggle pause. - public void TargetPauseToggle(object target) - { - List list; - if (tweens.TryGetValue(target, out list)) - { - for (int i = 0; i < list.Count; ++i) - list[i]?.PauseToggle(); - } - } - - /// - /// Resume all tweens with the given target. - /// - /// The object being tweened that you want to resume. - public void TargetResume(object target) - { - List list; - if (tweens.TryGetValue(target, out list)) - { - for (int i = 0; i < list.Count; ++i) - list[i]?.Resume(); - } - } - #endregion - - private class NumericLerper : MemberLerper - { - float from, to, range; - - public override void Initialize(object fromValue, object toValue, Behavior behavior) - { - from = Convert.ToSingle(fromValue); - to = Convert.ToSingle(toValue); - range = to - from; - - if ((behavior & Behavior.Rotation) == Behavior.Rotation) - { - float angle = from; - if ((behavior & Behavior.RotationRadians) == Behavior.RotationRadians) - angle *= DEG; - - if (angle < 0) - angle = 360 + angle; - - float r = angle + range; - float d = r - angle; - float a = (float) Math.Abs(d); - - if (a >= 180) range = (360 - a) * (d > 0 ? -1 : 1); - else range = d; - } - } - - public override object Interpolate(float t, object current, Behavior behavior) - { - var value = from + range * t; - if ((behavior & Behavior.Rotation) == Behavior.Rotation) - { - if ((behavior & Behavior.RotationRadians) == Behavior.RotationRadians) - value *= DEG; - - value %= 360.0f; - - if (value < 0) - value += 360.0f; - - if ((behavior & Behavior.RotationRadians) == Behavior.RotationRadians) - value *= RAD; - } - - if ((behavior & Behavior.Round) == Behavior.Round) - value = (float) Math.Round(value); - - var type = current.GetType(); - return Convert.ChangeType(value, type); - } - } - } - } + } + } + } } \ No newline at end of file