[NUI][API12] Keep DRGLView delegate at least 1 frame rendering
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 25 Mar 2025 04:57:43 +0000 (13:57 +0900)
committerEunki Hong <h.pichulia@gmail.com>
Wed, 9 Apr 2025 00:58:51 +0000 (09:58 +0900)
Backport patch for #6775 and #6826

Keep CustomAlphaFunction's delegate at least 1 frame

Let we collect all custom alpha functor when we all Animate() API.

If we call Clear() or Animation become Dispose(), let we move those
delegates reference to some global static holder, named `RenderThreadObjectHolder`.

The `RenderThreadObjectHolder` will clear the items after at least 1 frame updated.

We determine the frame updated by the Animation's finished callback.

Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
+

Keep DRGLView delegate at least 1 frame rendering

Since DRGLView's rendering callback invoked at render thread,
we need to keep those delegate at least 1 frame until those
delegate will never be called at native side.

We have good internal class for this case, named `RenderThreadObjectHolder`.

Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
src/Tizen.NUI/src/internal/Common/RenderThreadObjectHolder.cs [new file with mode: 0755]
src/Tizen.NUI/src/internal/NativeBinding/SWIGTYPE_p_f_float__float.cs [deleted file]
src/Tizen.NUI/src/public/Animation/AlphaFunction.cs
src/Tizen.NUI/src/public/Animation/Animation.cs
src/Tizen.NUI/src/public/BaseComponents/DirectRenderingGLView.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/FrameUpdateCallbackTest.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/SpringAnimationSample.cs

diff --git a/src/Tizen.NUI/src/internal/Common/RenderThreadObjectHolder.cs b/src/Tizen.NUI/src/internal/Common/RenderThreadObjectHolder.cs
new file mode 100755 (executable)
index 0000000..a60ec18
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright(c) 2025 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.ComponentModel;
+
+namespace Tizen.NUI
+{
+    /// <summary>
+    /// This is used to store a object reference until render thread rendered at least 1 times.
+    /// Note that Register itself should be called at main thread!
+    /// </summary>
+    internal sealed class RenderThreadObjectHolder : global::System.IDisposable
+    {
+        private static readonly RenderThreadObjectHolder renderThreadObjectHolder = new RenderThreadObjectHolder();
+
+        /// <summary>
+        /// static initialization singleton
+        /// </summary>
+        internal static RenderThreadObjectHolder Instance
+        {
+            get { return renderThreadObjectHolder; }
+        }
+
+        /// <summary>
+        /// Strong holder for the System.Delegate.
+        /// </summary>
+        private HashSet<System.Delegate> delegateSet;
+
+        private Animation threadCheckingAnimation;
+
+        private bool animationTriggered;
+
+        private RenderThreadObjectHolder()
+        {
+            delegateSet = new HashSet<System.Delegate>();
+        }
+
+        ~RenderThreadObjectHolder() => Dispose(false);
+
+        internal static void RegisterDelegates(List<System.Delegate> disposedDelgates)
+        {
+            if (disposedDelgates == null)
+            {
+                return;
+            }
+
+            foreach (var disposedDelgate in disposedDelgates)
+            {
+                RegisterDelegate(disposedDelgate);
+            }
+        }
+
+        internal static void RegisterDelegate(System.Delegate disposedDelgate)
+        {
+            var holder = Instance;
+
+            if (holder == null)
+            {
+                // Maybe applicatoin terminated case. We should ignore it.
+                return;
+            }
+
+            if (disposedDelgate == null)
+            {
+                return;
+            }
+
+            holder.delegateSet.Add(disposedDelgate);
+
+            if (!holder.animationTriggered)
+            {
+                if (holder.threadCheckingAnimation == null)
+                {
+                    // Make 0ms animation, and Play + Stop.
+                    holder.threadCheckingAnimation = new Animation(0);
+                    holder.threadCheckingAnimation.Finished += OnFinished;
+                }
+                holder.animationTriggered = true;
+                holder.threadCheckingAnimation.Play();
+                holder.threadCheckingAnimation.Stop();
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                threadCheckingAnimation?.Dispose();
+                NUILog.ErrorBacktrace("We should not manually dispose for singleton class!");
+            }
+        }
+
+        private static void OnFinished(object o, EventArgs e)
+        {
+            var holder = Instance;
+
+            if (holder == null)
+            {
+                // Maybe applicatoin terminated case. We should ignore it.
+                return;
+            }
+
+            // Clear objects now.
+            holder.delegateSet.Clear();
+
+            holder.animationTriggered = false;
+        }
+    }
+}
diff --git a/src/Tizen.NUI/src/internal/NativeBinding/SWIGTYPE_p_f_float__float.cs b/src/Tizen.NUI/src/internal/NativeBinding/SWIGTYPE_p_f_float__float.cs
deleted file mode 100755 (executable)
index eba9b85..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-//------------------------------------------------------------------------------
-// <auto-generated />
-//
-// This file was automatically generated by SWIG (http://www.swig.org).
-// Version 3.0.9
-//
-// Do not make changes to this file unless you know what you are doing--modify
-// the SWIG interface file instead.
-//------------------------------------------------------------------------------
-
-namespace Tizen.NUI
-{
-    internal class SWIGTYPE_p_f_float__float
-    {
-        private global::System.Runtime.InteropServices.HandleRef swigCPtr;
-
-        internal SWIGTYPE_p_f_float__float(global::System.IntPtr cPtr)
-        {
-            swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
-        }
-
-        protected SWIGTYPE_p_f_float__float()
-        {
-            swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
-        }
-
-        internal static global::System.Runtime.InteropServices.HandleRef getCPtr(SWIGTYPE_p_f_float__float obj)
-        {
-            return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
-        }
-    }
-}
index 227f105d7c1d1f2cfe1d6e842d1c681925341aea..5088d5cf2c550981a5cbe9d0c0543cbe349b4153 100755 (executable)
@@ -43,15 +43,22 @@ namespace Tizen.NUI
     /// <since_tizen> 3 </since_tizen>
     public class AlphaFunction : Disposable
     {
+        private static readonly object dummyObject = new object();
 
         /// <summary>
         /// The constructor.<br />
         /// Creates an alpha function object with the user-defined alpha function.<br />
         /// </summary>
         /// <param name="func">User defined function. It must be a method formatted as float alphafunction(float progress)</param>
+        /// <remarks>
+        /// Alpha function called at seperated thread with main thread.
+        /// Due to the disposed infomation is not send to seperated thread,
+        /// Given function might be invoked even if animation class or alpha functoin itself disposed.
+        /// </remarks>
         /// <since_tizen> 3 </since_tizen>
-        public AlphaFunction(global::System.Delegate func) : this(Interop.AlphaFunction.NewAlphaFunction(SWIGTYPE_p_f_float__float.getCPtr(new SWIGTYPE_p_f_float__float(System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate<System.Delegate>(func)))), true)
+        public AlphaFunction(global::System.Delegate func) : this(Interop.AlphaFunction.NewAlphaFunction(new global::System.Runtime.InteropServices.HandleRef(dummyObject, System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate<System.Delegate>(func))), true)
         {
+            CustomAlphaFunctionDelegate = func;
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
@@ -93,12 +100,6 @@ namespace Tizen.NUI
         {
         }
 
-        // Not used : This will be remained by 2021-05-30 to check side effect. After 2021-05-30 this will be removed cleanly
-        // internal AlphaFunction(SWIGTYPE_p_f_float__float function) : this(Interop.AlphaFunction.NewAlphaFunction(SWIGTYPE_p_f_float__float.getCPtr(function)), true)
-        // {
-        //     if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        // }
-
         /// <summary>
         /// This specifies the various types of built-in alpha functions available for animations.
         /// </summary>
@@ -204,6 +205,8 @@ namespace Tizen.NUI
             Bezier
         }
 
+        internal global::System.Delegate CustomAlphaFunctionDelegate { get; private set; }
+
         /// <summary>
         /// Retrieves the control points of the alpha function.<br />
         /// </summary>
@@ -326,15 +329,6 @@ namespace Tizen.NUI
             return propertyKey;
         }
 
-        // Not used : This will be remained by 2021-05-30 to check side effect. After 2021-05-30 this will be removed cleanly
-        // internal SWIGTYPE_p_f_float__float GetCustomFunction()
-        // {
-        //     global::System.IntPtr cPtr = Interop.AlphaFunction.GetCustomFunction(SwigCPtr);
-        //     SWIGTYPE_p_f_float__float ret = (cPtr == global::System.IntPtr.Zero) ? null : new SWIGTYPE_p_f_float__float(cPtr);
-        //     if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-        //     return ret;
-        // }
-
         /// This will not be public opened.
         [EditorBrowsable(EditorBrowsableState.Never)]
         protected override void ReleaseSwigCPtr(System.Runtime.InteropServices.HandleRef swigCPtr)
index 308560b121e1215eb0b0857a3140c760b2ebb68c..de78a9e4eb5b2e3d0a27036ea34ecf4f3226cee8 100755 (executable)
@@ -72,6 +72,8 @@ namespace Tizen.NUI
         private List<int> startTimeList = null;
         private List<int> endTimeList = null;
 
+        private List<System.Delegate> customAlphaFunctionDelegates = null;
+
         /// <summary>
         /// Creates an initialized animation.<br />
         /// The animation will not loop.<br />
@@ -1346,6 +1348,8 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public void Clear()
         {
+            ClearCustomAlphaFunctionDelegate();
+
             Interop.Animation.Clear(SwigCPtr);
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
@@ -1666,6 +1670,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateByAlphaFunction(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), AlphaFunction.getCPtr(alpha));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1679,6 +1684,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateBy(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(relativeValue), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1692,6 +1698,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateToAlphaFunction(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), AlphaFunction.getCPtr(alpha));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1705,6 +1712,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateTo(SwigCPtr, Property.getCPtr(target), PropertyValue.getCPtr(destinationValue), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1719,6 +1727,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateByAlphaFunction(SwigCPtr, Property.getCPtr(target), relativeValueIntPtr, AlphaFunction.getCPtr(alpha));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1732,6 +1741,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateBy(SwigCPtr, Property.getCPtr(target), relativeValueIntPtr, AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1745,6 +1755,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateToAlphaFunction(SwigCPtr, Property.getCPtr(target), destinationValueIntPtr, AlphaFunction.getCPtr(alpha));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1758,6 +1769,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateTo(SwigCPtr, Property.getCPtr(target), destinationValueIntPtr, AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1771,6 +1783,7 @@ namespace Tizen.NUI
 
         internal void AnimateBetween(Property target, KeyFrames keyFrames, AlphaFunction alpha)
         {
+            AppendCustomAlphaFunctionDelegate(alpha?.CustomAlphaFunctionDelegate);
             Interop.Animation.AnimateBetweenAlphaFunction(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha));
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
@@ -1783,6 +1796,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateBetweenAlphaFunctionInterpolation(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), (int)interpolation);
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1796,6 +1810,7 @@ namespace Tizen.NUI
 
         internal void AnimateBetween(Property target, KeyFrames keyFrames, AlphaFunction alpha, TimePeriod period)
         {
+            AppendCustomAlphaFunctionDelegate(alpha?.CustomAlphaFunctionDelegate);
             Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
@@ -1808,6 +1823,7 @@ namespace Tizen.NUI
             }
             else
             {
+                AppendCustomAlphaFunctionDelegate(alpha.CustomAlphaFunctionDelegate);
                 Interop.Animation.AnimateBetween(SwigCPtr, Property.getCPtr(target), KeyFrames.getCPtr(keyFrames), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period), (int)interpolation);
             }
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
@@ -1821,6 +1837,7 @@ namespace Tizen.NUI
 
         internal void Animate(View view, Path path, Vector3 forward, AlphaFunction alpha)
         {
+            AppendCustomAlphaFunctionDelegate(alpha?.CustomAlphaFunctionDelegate);
             Interop.Animation.AnimateAlphaFunction(SwigCPtr, View.getCPtr(view), Path.getCPtr(path), Vector3.getCPtr(forward), AlphaFunction.getCPtr(alpha));
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
@@ -1833,6 +1850,7 @@ namespace Tizen.NUI
 
         internal void Animate(View view, Path path, Vector3 forward, AlphaFunction alpha, TimePeriod period)
         {
+            AppendCustomAlphaFunctionDelegate(alpha?.CustomAlphaFunctionDelegate);
             Interop.Animation.Animate(SwigCPtr, View.getCPtr(view), Path.getCPtr(path), Vector3.getCPtr(forward), AlphaFunction.getCPtr(alpha), TimePeriod.getCPtr(period));
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
@@ -1876,6 +1894,8 @@ namespace Tizen.NUI
                 animationProgressReachedEventCallback = null;
             }
 
+            ClearCustomAlphaFunctionDelegate();
+
             base.Dispose(type);
         }
 
@@ -1891,6 +1911,30 @@ namespace Tizen.NUI
             Interop.Animation.DeleteAnimation(swigCPtr);
         }
 
+        private void AppendCustomAlphaFunctionDelegate(global::System.Delegate customFunction)
+        {
+            if (customFunction == null)
+            {
+                return;
+            }
+
+            if (customAlphaFunctionDelegates == null)
+            {
+                customAlphaFunctionDelegates = new List<System.Delegate>();
+            }
+            customAlphaFunctionDelegates.Add(customFunction);
+        }
+
+        private void ClearCustomAlphaFunctionDelegate()
+        {
+            if (customAlphaFunctionDelegates != null)
+            {
+                // Delete function delegates after 1 frame rendered.
+                RenderThreadObjectHolder.RegisterDelegates(customAlphaFunctionDelegates);
+                customAlphaFunctionDelegates = null;
+            }
+        }
+
         private void OnFinished(IntPtr data)
         {
             if (animationFinishedEventHandler != null)
index 8c3bff9f488bbbae51b18cbd6f6ca5285d6d08d6..cf604f417daa6b3a1a361958c5606c70c1ead8d9 100644 (file)
@@ -291,6 +291,36 @@ namespace Tizen.NUI.BaseComponents
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
+        /// <summary>
+        /// you can override it to clean-up your own resources.
+        /// </summary>
+        /// <param name="type">DisposeTypes</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected override void Dispose(DisposeTypes type)
+        {
+            if (Disposed)
+            {
+                return;
+            }
+
+            if (type == DisposeTypes.Explicit)
+            {
+                //Called by User
+                //Release your own managed resources here.
+                //You should release all of your own disposable objects here.
+
+                // Keep delegate life ownership until next frame rendered.
+                RenderThreadObjectHolder.RegisterDelegate(glInitializeCallback);
+                RenderThreadObjectHolder.RegisterDelegate(glRenderFrameCallback);
+                RenderThreadObjectHolder.RegisterDelegate(glTerminateCallback);
+                RenderThreadObjectHolder.RegisterDelegate(internalRenderFrameCallback);
+
+                // DevNote : Do not make glRenderFrameCallback as null here, to avoid race condition or lock overhead.
+            }
+
+            base.Dispose(type);
+        }
+
         private int OnRenderFrame(global::System.IntPtr cPtr)
         {
             if (glRenderFrameCallback != null)
index 7e0aa690d03ed5b90ddc7350ea51e6df37951267..ddcec1ca36b71a864c6f9461c7aced581a9214aa 100644 (file)
@@ -755,10 +755,13 @@ namespace Tizen.NUI.Samples
             finishAnimation.Clear();
             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
             finishAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
-            GC.KeepAlive(customScrollAlphaFunction);
             finishAnimation.Duration = (int)finishAnimationDuration;
-            finishAnimation.AnimateTo(controlView, "PositionX", destination);
+            finishAnimation.AnimateTo(controlView, "PositionX", destination, finishAnimation.DefaultAlphaFunction);
             finishAnimation.Play();
+
+            // Remove alpha function now, for test UserAlphaFunctionDelegate alived. 
+            customScrollAlphaFunction = null;
+            finishAnimation.DefaultAlphaFunction?.Dispose();
         }
 
         private float CustomScrollAlphaFunction(float progress)
index 51a3974c1a62dc46ca5b65fb842ee6c85fe12edb..4c633402123244c9d63fc739dc732af79c7c58bb 100644 (file)
@@ -225,6 +225,10 @@ namespace Tizen.NUI.Samples
                 }
             };
 
+            // Remove alpha function now, for test UserAlphaFunctionDelegate alived. 
+            customSpringAlphaFunction = null;
+            customAlphaFunction?.Dispose();
+
             window.Add(root);
         }