[NUI][API12] Support RemoveIdle API for NUIApplication + Use unified idler callback
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 18 Dec 2024 04:59:07 +0000 (13:59 +0900)
committerEunki Hong <h.pichulia@gmail.com>
Thu, 19 Dec 2024 01:40:13 +0000 (10:40 +0900)
Let we keep Idler callback list as membery of internal Application class,
and allow to remove them during idler callback execute.

Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
src/Tizen.NUI/src/internal/Application/Application.cs
src/Tizen.NUI/src/internal/Application/NUICoreBackend.cs
src/Tizen.NUI/src/public/Application/NUIApplication.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/FlushApplicationMessageSample.cs

index 4d775bc957952cc0c509d51342438bf80280f98f..5c6669b9518a5cd7a48caf9fca8b3328a59c43b0 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright(c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright(c) 2024 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.
@@ -363,6 +363,47 @@ namespace Tizen.NUI
                 OnResourcesChanged(changedResources);
         }
 
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        private delegate void RootIdleCallbackType();
+
+        private RootIdleCallbackType rootIdleCallback = null;
+        private Dictionary<System.Delegate, bool> idleCallbackMap = new Dictionary<System.Delegate, bool>();
+
+        private void RootIdleCallback()
+        {
+            if (idleCallbackMap == null || IsDisposedOrQueued)
+            {
+                Tizen.Log.Error("NUI", $"[Error] Application disposed! Fail to execute idle callback!\n");
+                return;
+            }
+
+            Tizen.Log.Debug("NUI", $"Application RootIdleCallback comes\n");
+            // Reset root idle callback as null now, since we could call AddIdle during delegate function invoke
+            rootIdleCallback = null;
+
+            // Copy key list of idle callback map
+            // (Since idle callback could change the dictionary itself during iteration, we need to make a copy of keys first)
+            List<System.Delegate> delegateList = new List<System.Delegate>();
+            foreach (var func in idleCallbackMap.Keys)
+            {
+                delegateList.Add(func);
+            }
+
+            foreach (var func in delegateList)
+            {
+                // Remove delegate at map first, and then invoke it.
+                bool isValid = false;
+                if (idleCallbackMap?.Remove(func, out isValid) ?? false)
+                {
+                    if (isValid)
+                    {
+                        func.DynamicInvoke();
+                    }
+                }
+            }
+            Tizen.Log.Debug("NUI", $"Application RootIdleCallback finished\n");
+        }
+
         internal Application(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
         {
             SetCurrentApplication(this);
@@ -376,145 +417,150 @@ namespace Tizen.NUI
                 return;
             }
 
-            //Release your own unmanaged resources here.
-            //You should not access any managed member here except static instance.
-            //because the execution order of Finalizes is non-deterministic.
-            if (applicationInitEventCallbackDelegate != null)
+            if (type == DisposeTypes.Explicit)
             {
-                initSignal?.Disconnect(applicationInitEventCallbackDelegate);
-                initSignal?.Dispose();
-                initSignal = null;
-            }
+                //Release your own unmanaged resources here.
+                //You should not access any managed member here except static instance.
+                //because the execution order of Finalizes is non-deterministic.
+                if (applicationInitEventCallbackDelegate != null)
+                {
+                    initSignal?.Disconnect(applicationInitEventCallbackDelegate);
+                    initSignal?.Dispose();
+                    initSignal = null;
+                }
 
-            if (applicationTerminateEventCallbackDelegate != null)
-            {
-                terminateSignal?.Disconnect(applicationTerminateEventCallbackDelegate);
-                terminateSignal?.Dispose();
-                terminateSignal = null;
-            }
+                if (applicationTerminateEventCallbackDelegate != null)
+                {
+                    terminateSignal?.Disconnect(applicationTerminateEventCallbackDelegate);
+                    terminateSignal?.Dispose();
+                    terminateSignal = null;
+                }
 
-            if (applicationPauseEventCallbackDelegate != null)
-            {
-                pauseSignal?.Disconnect(applicationPauseEventCallbackDelegate);
-                pauseSignal?.Dispose();
-                pauseSignal = null;
-            }
+                if (applicationPauseEventCallbackDelegate != null)
+                {
+                    pauseSignal?.Disconnect(applicationPauseEventCallbackDelegate);
+                    pauseSignal?.Dispose();
+                    pauseSignal = null;
+                }
 
-            if (applicationResumeEventCallbackDelegate != null)
-            {
-                resumeSignal?.Disconnect(applicationResumeEventCallbackDelegate);
-                resumeSignal?.Dispose();
-                resumeSignal = null;
-            }
+                if (applicationResumeEventCallbackDelegate != null)
+                {
+                    resumeSignal?.Disconnect(applicationResumeEventCallbackDelegate);
+                    resumeSignal?.Dispose();
+                    resumeSignal = null;
+                }
 
-            if (applicationResetEventCallbackDelegate != null)
-            {
-                resetSignal?.Disconnect(applicationResetEventCallbackDelegate);
-                resetSignal?.Dispose();
-                resetSignal = null;
-            }
+                if (applicationResetEventCallbackDelegate != null)
+                {
+                    resetSignal?.Disconnect(applicationResetEventCallbackDelegate);
+                    resetSignal?.Dispose();
+                    resetSignal = null;
+                }
 
-            if (applicationLanguageChangedEventCallbackDelegate != null)
-            {
-                languageChangedSignal?.Disconnect(applicationLanguageChangedEventCallbackDelegate);
-                languageChangedSignal?.Dispose();
-                languageChangedSignal = null;
-            }
+                if (applicationLanguageChangedEventCallbackDelegate != null)
+                {
+                    languageChangedSignal?.Disconnect(applicationLanguageChangedEventCallbackDelegate);
+                    languageChangedSignal?.Dispose();
+                    languageChangedSignal = null;
+                }
 
-            if (applicationRegionChangedEventCallbackDelegate != null)
-            {
-                regionChangedSignal?.Disconnect(applicationRegionChangedEventCallbackDelegate);
-                regionChangedSignal?.Dispose();
-                regionChangedSignal = null;
-            }
+                if (applicationRegionChangedEventCallbackDelegate != null)
+                {
+                    regionChangedSignal?.Disconnect(applicationRegionChangedEventCallbackDelegate);
+                    regionChangedSignal?.Dispose();
+                    regionChangedSignal = null;
+                }
 
-            if (applicationBatteryLowEventCallbackDelegate != null)
-            {
-                batteryLowSignal?.Disconnect(applicationBatteryLowEventCallbackDelegate);
-                batteryLowSignal?.Dispose();
-                batteryLowSignal = null;
-            }
+                if (applicationBatteryLowEventCallbackDelegate != null)
+                {
+                    batteryLowSignal?.Disconnect(applicationBatteryLowEventCallbackDelegate);
+                    batteryLowSignal?.Dispose();
+                    batteryLowSignal = null;
+                }
 
-            if (applicationMemoryLowEventCallbackDelegate != null)
-            {
-                memoryLowSignal?.Disconnect(applicationMemoryLowEventCallbackDelegate);
-                memoryLowSignal?.Dispose();
-                memoryLowSignal = null;
-            }
+                if (applicationMemoryLowEventCallbackDelegate != null)
+                {
+                    memoryLowSignal?.Disconnect(applicationMemoryLowEventCallbackDelegate);
+                    memoryLowSignal?.Dispose();
+                    memoryLowSignal = null;
+                }
 
-            if (applicationDeviceOrientationChangedEventCallback != null)
-            {
-                deviceOrientationChangedSignal?.Disconnect(applicationDeviceOrientationChangedEventCallback);
-                deviceOrientationChangedSignal?.Dispose();
-                deviceOrientationChangedSignal = null;
-            }
+                if (applicationDeviceOrientationChangedEventCallback != null)
+                {
+                    deviceOrientationChangedSignal?.Disconnect(applicationDeviceOrientationChangedEventCallback);
+                    deviceOrientationChangedSignal?.Dispose();
+                    deviceOrientationChangedSignal = null;
+                }
 
-            if (applicationAppControlEventCallbackDelegate != null)
-            {
-                appControlSignal?.Disconnect(applicationAppControlEventCallbackDelegate);
-                appControlSignal?.Dispose();
-                appControlSignal = null;
-            }
+                if (applicationAppControlEventCallbackDelegate != null)
+                {
+                    appControlSignal?.Disconnect(applicationAppControlEventCallbackDelegate);
+                    appControlSignal?.Dispose();
+                    appControlSignal = null;
+                }
 
-            //Task
-            if (applicationTaskInitEventCallbackDelegate != null)
-            {
-                taskInitSignal?.Disconnect(applicationTaskInitEventCallbackDelegate);
-                taskInitSignal?.Dispose();
-                taskInitSignal = null;
-            }
+                //Task
+                if (applicationTaskInitEventCallbackDelegate != null)
+                {
+                    taskInitSignal?.Disconnect(applicationTaskInitEventCallbackDelegate);
+                    taskInitSignal?.Dispose();
+                    taskInitSignal = null;
+                }
 
-            if (applicationTaskTerminateEventCallbackDelegate != null)
-            {
-                taskTerminateSignal?.Disconnect(applicationTaskTerminateEventCallbackDelegate);
-                taskTerminateSignal?.Dispose();
-                taskTerminateSignal = null;
-            }
+                if (applicationTaskTerminateEventCallbackDelegate != null)
+                {
+                    taskTerminateSignal?.Disconnect(applicationTaskTerminateEventCallbackDelegate);
+                    taskTerminateSignal?.Dispose();
+                    taskTerminateSignal = null;
+                }
 
-            if (applicationTaskLanguageChangedEventCallbackDelegate != null)
-            {
-                taskLanguageChangedSignal?.Disconnect(applicationTaskLanguageChangedEventCallbackDelegate);
-                taskLanguageChangedSignal?.Dispose();
-                taskLanguageChangedSignal = null;
-            }
+                if (applicationTaskLanguageChangedEventCallbackDelegate != null)
+                {
+                    taskLanguageChangedSignal?.Disconnect(applicationTaskLanguageChangedEventCallbackDelegate);
+                    taskLanguageChangedSignal?.Dispose();
+                    taskLanguageChangedSignal = null;
+                }
 
-            if (applicationTaskRegionChangedEventCallbackDelegate != null)
-            {
-                taskRegionChangedSignal?.Disconnect(applicationTaskRegionChangedEventCallbackDelegate);
-                taskRegionChangedSignal?.Dispose();
-                taskRegionChangedSignal = null;
-            }
+                if (applicationTaskRegionChangedEventCallbackDelegate != null)
+                {
+                    taskRegionChangedSignal?.Disconnect(applicationTaskRegionChangedEventCallbackDelegate);
+                    taskRegionChangedSignal?.Dispose();
+                    taskRegionChangedSignal = null;
+                }
 
-            if (applicationTaskBatteryLowEventCallbackDelegate != null)
-            {
-                taskBatteryLowSignal?.Disconnect(applicationTaskBatteryLowEventCallbackDelegate);
-                taskBatteryLowSignal?.Dispose();
-                taskBatteryLowSignal = null;
-            }
+                if (applicationTaskBatteryLowEventCallbackDelegate != null)
+                {
+                    taskBatteryLowSignal?.Disconnect(applicationTaskBatteryLowEventCallbackDelegate);
+                    taskBatteryLowSignal?.Dispose();
+                    taskBatteryLowSignal = null;
+                }
 
-            if (applicationTaskMemoryLowEventCallbackDelegate != null)
-            {
-                taskMemoryLowSignal?.Disconnect(applicationTaskMemoryLowEventCallbackDelegate);
-                taskMemoryLowSignal?.Dispose();
-                taskMemoryLowSignal = null;
-            }
+                if (applicationTaskMemoryLowEventCallbackDelegate != null)
+                {
+                    taskMemoryLowSignal?.Disconnect(applicationTaskMemoryLowEventCallbackDelegate);
+                    taskMemoryLowSignal?.Dispose();
+                    taskMemoryLowSignal = null;
+                }
 
-            if (applicationTaskDeviceOrientationChangedEventCallback != null)
-            {
-                taskDeviceOrientationChangedSignal?.Disconnect(applicationTaskDeviceOrientationChangedEventCallback);
-                taskDeviceOrientationChangedSignal?.Dispose();
-                taskDeviceOrientationChangedSignal = null;
-            }
+                if (applicationTaskDeviceOrientationChangedEventCallback != null)
+                {
+                    taskDeviceOrientationChangedSignal?.Disconnect(applicationTaskDeviceOrientationChangedEventCallback);
+                    taskDeviceOrientationChangedSignal?.Dispose();
+                    taskDeviceOrientationChangedSignal = null;
+                }
 
-            if (applicationTaskAppControlEventCallbackDelegate != null)
-            {
-                taskAppControlSignal?.Disconnect(applicationTaskAppControlEventCallbackDelegate);
-                taskAppControlSignal?.Dispose();
-                taskAppControlSignal = null;
+                if (applicationTaskAppControlEventCallbackDelegate != null)
+                {
+                    taskAppControlSignal?.Disconnect(applicationTaskAppControlEventCallbackDelegate);
+                    taskAppControlSignal?.Dispose();
+                    taskAppControlSignal = null;
+                }
+
+                window?.Dispose();
+                window = null;
             }
 
-            window?.Dispose();
-            window = null;
+            idleCallbackMap = null;
 
             base.Dispose(type);
         }
@@ -1721,13 +1767,76 @@ namespace Tizen.NUI
         /// </remarks>
         public bool AddIdle(System.Delegate func)
         {
-            System.IntPtr ip = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate<System.Delegate>(func);
-            System.IntPtr ip2 = Interop.Application.MakeCallback(new System.Runtime.InteropServices.HandleRef(this, ip));
+            bool idleAdded = false; // default value is false
 
-            bool ret = Interop.Application.AddIdle(SwigCPtr, new System.Runtime.InteropServices.HandleRef(this, ip2));
+            if (idleCallbackMap == null || IsDisposedOrQueued)
+            {
+                Tizen.Log.Error("NUI", $"[Error] Application disposed! failed to add idle {func}\n");
+                return false;
+            }
 
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
+            // Register root idle callback for Tizen.NUI.Application
+            if (rootIdleCallback == null)
+            {
+                rootIdleCallback = RootIdleCallback;
+                System.IntPtr ip = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate<System.Delegate>(rootIdleCallback);
+                System.IntPtr ip2 = Interop.Application.MakeCallback(new System.Runtime.InteropServices.HandleRef(this, ip));
+
+                bool ret = Interop.Application.AddIdle(SwigCPtr, new System.Runtime.InteropServices.HandleRef(this, ip2));
+                NDalicPINVOKE.ThrowExceptionIfExists();
+                if (!ret)
+                {
+                    rootIdleCallback = null;
+                    Tizen.Log.Error("NUI", $"[Error] failed to add idle {func}\n");
+                    return false;
+                }
+            }
+
+            if (idleCallbackMap.TryGetValue(func, out bool isValid))
+            {
+                idleAdded = true; // success case
+                if (!isValid)
+                {
+                    idleCallbackMap[func] = true;
+                }
+                else
+                {
+                    Tizen.Log.Debug("NUI", $"Already added idle {func}\n");
+                }
+            }
+            else if (idleCallbackMap.TryAdd(func, true))
+            {
+                idleAdded = true; // success case
+            }
+
+            if (!idleAdded)
+            {
+                Tizen.Log.Error("NUI", $"[Error] failed to add idle {func}\n");
+            }
+
+            return idleAdded;
+        }
+
+        /// <summary>
+        /// Remove delegate what we added by AddIdle.
+        /// </summary>
+        /// <param name="func">The function to remove</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void RemoveIdle(System.Delegate func)
+        {
+            if (idleCallbackMap == null || IsDisposedOrQueued)
+            {
+                Tizen.Log.Error("NUI", $"[Error] Application disposed! failed to remove idle {func}\n");
+                return;
+            }
+
+            if (idleCallbackMap.TryGetValue(func, out bool isValid))
+            {
+                if (isValid)
+                {
+                    idleCallbackMap[func] = false;
+                }
+            }
         }
 
         /**
@@ -1925,13 +2034,6 @@ namespace Tizen.NUI
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
-        internal bool AddIdle(SWIGTYPE_p_Dali__CallbackBase callback)
-        {
-            bool ret = Interop.Application.AddIdle(SwigCPtr, SWIGTYPE_p_Dali__CallbackBase.getCPtr(callback));
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
-        }
-
         public Window GetWindow()
         {
             if (window != null)
index b994234e665e1e9ab1359d6998b8c044c040be62..75237ffd4a9dbb2c34e8b88ebbdf3a2d37c05fcb 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
@@ -166,6 +166,16 @@ namespace Tizen.NUI
             return application.AddIdle(func);
         }
 
+        /// <summary>
+        /// Remove delegate what we added by AddIdle.
+        /// </summary>
+        /// <param name="func">The function to remove</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void RemoveIdle(System.Delegate func)
+        {
+            application.RemoveIdle(func);
+        }
+
         /// <summary>
         /// The Run application.
         /// </summary>
index ac178056c5faf762821a934a3587dbb97d47bb91..b63fab53542f221cef85cea15d168f8e11af711c 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
@@ -547,6 +547,16 @@ namespace Tizen.NUI
             return ((NUICoreBackend)this.Backend).AddIdle(func);
         }
 
+        /// <summary>
+        /// Remove delegate what we added by AddIdle.
+        /// </summary>
+        /// <param name="func">The function to remove</param>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public void RemoveIdle(System.Delegate func)
+        {
+            ((NUICoreBackend)this.Backend).RemoveIdle(func);
+        }
+
         /// <summary>
         /// Flush render/update thread messages synchronously.
         /// </summary>
index 3b084621e78551889c939d075f03de73fb5627ba..e8900063b2552ef7915cad5d9a1857fbf1e71f16 100644 (file)
@@ -1,6 +1,7 @@
 using Tizen.NUI;
 using Tizen.NUI.BaseComponents;
 using Tizen.NUI.Components;
+using System;
 using System.Threading;
 
 namespace Tizen.NUI.Samples
@@ -23,6 +24,21 @@ namespace Tizen.NUI.Samples
             return application;
         }
 
+        private delegate void FuncDelegate();
+        private FuncDelegate FuncDelegater = null;
+
+        private void Func()
+        {
+            Tizen.Log.Error("NUITEST", "Idle callback comes!\n");
+            FuncDelegater = null;
+        }
+        private void Func2()
+        {
+            Tizen.Log.Error("NUITEST", "Idle callback comes! Register idle once again!\n");
+            FuncDelegater = Func;
+            GetCurrentApplication()?.AddIdle(FuncDelegater);
+        }
+
         public void Activate()
         {
             Window window = NUIApplication.GetDefaultWindow();
@@ -55,14 +71,91 @@ namespace Tizen.NUI.Samples
             Tizen.Log.Error("NUITEST", "Sleep done\n");
 
             textLabel.Text = "Sleep done!\n";
+
+            window.KeyEvent += WinKeyEvent;
         }
 
         public void Deactivate()
         {
             if (root != null)
             {
-                NUIApplication.GetDefaultWindow().Remove(root);
+                Window window = NUIApplication.GetDefaultWindow();
+                window.Remove(root);
+                window.KeyEvent -= WinKeyEvent;
                 root.Dispose();
+
+                if (FuncDelegater != null)
+                {
+                    GetCurrentApplication()?.RemoveIdle(FuncDelegater);
+                }
+            }
+        }
+
+        private void WinKeyEvent(object sender, Window.KeyEventArgs e)
+        {
+            if (e.Key.State == Key.StateType.Down)
+            {
+                if (e.Key.KeyPressedName == "1")
+                {
+                    Tizen.Log.Error("NUITEST", "Add idle callback\n");
+                    if (FuncDelegater == null)
+                    {
+                        FuncDelegater = Func;
+                    }
+
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                }
+                else if (e.Key.KeyPressedName == "2")
+                {
+                    Tizen.Log.Error("NUITEST", "Add idle callback, and remove immediately\n");
+                    if (FuncDelegater == null)
+                    {
+                        FuncDelegater = Func;
+                    }
+
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                    GetCurrentApplication()?.RemoveIdle(FuncDelegater);
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                    GetCurrentApplication()?.RemoveIdle(FuncDelegater);
+                    FuncDelegater = null;
+                }
+                else if (e.Key.KeyPressedName == "3")
+                {
+                    Tizen.Log.Error("NUITEST", "Add idle callback during idle callback execute\n");
+                    if (FuncDelegater == null)
+                    {
+                        FuncDelegater = Func2;
+                    }
+
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                }
+                else if (e.Key.KeyPressedName == "4")
+                {
+                    Tizen.Log.Error("NUITEST", "Add multiple idle callback times\n");
+                    if (FuncDelegater == null)
+                    {
+                        FuncDelegater = Func;
+                    }
+
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                    GetCurrentApplication()?.RemoveIdle(FuncDelegater);
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+
+                    FuncDelegater = Func2;
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                }
+                else if (e.Key.KeyPressedName == "5")
+                {
+                    Tizen.Log.Error("NUITEST", "Self dispose after add idle callback\n");
+                    if (FuncDelegater == null)
+                    {
+                        FuncDelegater = Func;
+                    }
+
+                    GetCurrentApplication()?.AddIdle(FuncDelegater);
+                    GetCurrentApplication()?.Dispose();
+                }
             }
         }
     }