Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
6402f3a
Remove continous press code from Application
tznind Jun 3, 2025
a7a5d1a
WIP prototype code to handle continuous press as subcomponent of View
tznind Jun 3, 2025
8f43909
Prototype with Button
tznind Jun 3, 2025
cbb20ff
Implement CWP
tznind Jun 3, 2025
b91a9ca
Move to seperate classes and prevent double entry to Start
tznind Jun 4, 2025
a19145a
Fix repeat clicking when moving mouse by removing phantom click code …
tznind Jun 4, 2025
d8335c4
Remove initial tick because it results in double activation e.g. butt…
tznind Jun 4, 2025
98947ba
Refactor DatePicker lamdas
tznind Jun 14, 2025
e946638
Merge branch 'v2_develop' into 4101_continuous_press
tznind Jun 14, 2025
550ef03
WIP investigate subcomponents instead of statics
tznind Jun 14, 2025
a28cc6c
Add IMouseGrabHandler to IApplication
tznind Jun 14, 2025
6d794f2
Make mouse grabbing non static activity
tznind Jun 14, 2025
ee7ea86
Make MouseHeldDown suppress when null fields e.g. app not initialized…
tznind Jun 14, 2025
887631d
Update test and remove dependency on Application
tznind Jun 14, 2025
0d75ef0
Fix other mouse click and hold tests
tznind Jun 14, 2025
c3c6c70
Code cleanup
tznind Jun 14, 2025
f717be5
Update class diagram
tznind Jun 14, 2025
32d747a
Fix bad xml doc references
tznind Jun 14, 2025
e9a33cb
Fix timed events not getting passed through in v2 applications
tznind Jun 14, 2025
48e80f3
Make timed events nullable for tests that dont create an Application
tznind Jun 14, 2025
06e45e1
Remove strange blocking test
tznind Jun 15, 2025
c106ff0
WIP remove all idles and replace with zero timeouts
tznind Jun 19, 2025
6f11fd6
Fix build of tests
tznind Jun 19, 2025
c597454
Fix unit tests
tznind Jun 20, 2025
aba2e43
Add wakeup call back in
tznind Jun 20, 2025
2dfe6b3
Comment out incredibly complicated test and fix others
tznind Jun 20, 2025
f18b752
Fix test
tznind Jun 20, 2025
34bc316
test fix
tznind Jun 20, 2025
0016a18
Make Post execute immediately if already on UI thread
tznind Jun 20, 2025
e85cccd
Re enable test and simplify Invoke to just execute if in UI thread (u…
tznind Jun 20, 2025
e4c7f0f
Remove xml doc references to idles
tznind Jun 21, 2025
e13ed63
Remove more references to idles
tznind Jun 21, 2025
c588e04
Make Screen initialization threadsafe
tznind Jun 21, 2025
c2390ad
merge 4101_continuous_press into branch
tznind Jun 21, 2025
49dc897
Add more exciting timeouts
tznind Jun 21, 2025
394794a
WIP add tests
tznind Jun 21, 2025
308b26f
fix log
tznind Jun 21, 2025
e20b489
fix test
tznind Jun 21, 2025
78c5d0d
make continuous key press use smoth acceleration
tznind Jun 21, 2025
966178e
Rename _lock to _lockScreen
tznind Jun 21, 2025
8721ea3
Remove section on idles, they are not a thing anymore - and they kind…
tznind Jun 21, 2025
53de140
Add nullable enable
tznind Jun 21, 2025
7fb15fa
Add xml comment
tznind Jun 21, 2025
889e071
Fix namings and cleanup code
tznind Jun 21, 2025
77e8675
Merge branch 'v2_develop' into logarithmic-timeout
tig Jun 24, 2025
144b027
Merge branch 'v2_develop' into logarithmic-timeout
tig Jun 27, 2025
de953f7
Merge branch 'v2_develop' into logarithmic-timeout
tznind Jul 5, 2025
f6b9fe7
xmldoc fix
tznind Jul 5, 2025
65207ad
Rename LockAndRunTimers to just RunTimers
tznind Jul 5, 2025
079a4c5
Rename AddTimeout and RemoveTimeout (and event) to just Add/Remove
tznind Jul 5, 2025
768e5bd
Update description of MainLoop
tznind Jul 5, 2025
fb19bfa
Merge branch 'v2_develop' into logarithmic-timeout
tig Jul 7, 2025
87fe518
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Jul 7, 2025
62fd462
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
tig Jul 7, 2025
1efaf49
Merge branch 'v2_develop' into logarithmic-timeout
tig Jul 7, 2025
837298a
Merge branch 'logarithmic-timeout' of github.com:tznind/gui.cs into t…
tig Jul 7, 2025
a1ea545
Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver
tig Jul 7, 2025
8abb13c
Again? Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_…
tig Jul 7, 2025
6515d88
Revert Commented out Run_T_Call_Init_ForceDriver_Should_Pick_Correct_…
tig Jul 7, 2025
a938fc1
When mouse is released from MouseHeldDown reset host MouseState
tznind Jul 9, 2025
ad8248a
Fix namespaces in class diagram
tznind Jul 10, 2025
ee22ef8
Apply @BDisp suggested fix
tznind Jul 10, 2025
d47defc
Fix class diagrams
tznind Jul 10, 2025
c477cce
Add lock
tznind Jul 10, 2025
28787e8
Make TimeSpan.Zero definetly run
tznind Jul 10, 2025
5fc2e48
Fix duplicate entry in package props
tznind Jul 10, 2025
47aa0cd
Code cleanup and better API docs
tig Jul 10, 2025
6894b58
Merge pull request #178 from tig/tznind-logarithmic-timeout
tznind Jul 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP remove all idles and replace with zero timeouts
  • Loading branch information
tznind committed Jun 19, 2025
commit c106ff035fbc73c247c17c44f633a01e4fac1f17
6 changes: 0 additions & 6 deletions Terminal.Gui/App/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,4 @@ internal static void ResetState (bool ignoreDisposed = false)
// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
SynchronizationContext.SetSynchronizationContext (null);
}

/// <summary>
/// Adds specified idle handler function to main iteration processing. The handler function will be called
/// once per iteration of the main loop after other events have been handled.
/// </summary>
public static void AddIdle (Func<bool> func) { ApplicationImpl.Instance.AddIdle (func); }
}
16 changes: 1 addition & 15 deletions Terminal.Gui/App/ApplicationImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public virtual void RequestStop (Toplevel? top)
/// <inheritdoc />
public virtual void Invoke (Action action)
{
Application.MainLoop?.AddIdle (
Application.AddTimeout (TimeSpan.Zero,
() =>
{
action ();
Expand All @@ -274,20 +274,6 @@ public virtual void Invoke (Action action)
/// <inheritdoc />
public bool IsLegacy { get; protected set; } = true;

/// <inheritdoc />
public virtual void AddIdle (Func<bool> func)
{
if (Application.MainLoop is null)
{
throw new NotInitializedException ("Cannot add idle before main loop is initialized");
}

// Yes in this case we cannot go direct via TimedEvents because legacy main loop
// has established behaviour to do other stuff too e.g. 'wake up'.
Application.MainLoop.AddIdle (func);

}

/// <inheritdoc />
public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
{
Expand Down
8 changes: 1 addition & 7 deletions Terminal.Gui/App/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,7 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
/// is cutting edge.
/// </summary>
bool IsLegacy { get; }

/// <summary>
/// Adds specified idle handler function to main iteration processing. The handler function will be called
/// once per iteration of the main loop after other events have been handled.
/// </summary>
void AddIdle (Func<bool> func);


/// <summary>Adds a timeout to the application.</summary>
/// <remarks>
/// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
Expand Down
54 changes: 13 additions & 41 deletions Terminal.Gui/App/ITimedEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,11 @@ namespace Terminal.Gui.App;
/// </summary>
public interface ITimedEvents
{
/// <summary>
/// Adds specified idle handler function to main iteration processing. The handler function will be called
/// once per iteration of the main loop after other events have been handled.
/// </summary>
/// <param name="idleHandler"></param>
void AddIdle (Func<bool> idleHandler);

/// <summary>
/// Runs all idle hooks
/// </summary>
void LockAndRunIdles ();

/// <summary>
/// Runs all timeouts that are due
/// </summary>
void LockAndRunTimers ();

/// <summary>
/// Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
/// handlers.
/// </summary>
/// <param name="waitTimeout">
/// Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
/// there are no active timers.
/// </param>
/// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
bool CheckTimersAndIdleHandlers (out int waitTimeout);

/// <summary>Adds a timeout to the application.</summary>
/// <remarks>
/// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
Expand All @@ -58,33 +35,28 @@ public interface ITimedEvents
/// </returns>
bool RemoveTimeout (object token);

/// <summary>
/// Returns all currently registered idles. May not include
/// actively executing idles.
/// </summary>
ReadOnlyCollection<Func<bool>> IdleHandlers { get;}

/// <summary>
/// Returns the next planned execution time (key - UTC ticks)
/// for each timeout that is not actively executing.
/// </summary>
SortedList<long, Timeout> Timeouts { get; }


/// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
/// <returns>
/// <see langword="true"/>
/// if the idle handler is successfully removed; otherwise,
/// <see langword="false"/>
/// .
/// This method also returns
/// <see langword="false"/>
/// if the idle handler is not found.</returns>
bool RemoveIdle (Func<bool> fnTrue);

/// <summary>
/// Invoked when a new timeout is added. To be used in the case when
/// <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
/// </summary>
event EventHandler<TimeoutEventArgs>? TimeoutAdded;



/// <summary>
/// Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers
/// handlers.
/// </summary>
/// <param name="waitTimeout">
/// Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
/// there are no active timers.
/// </param>
/// <returns><see langword="true"/> if there is a timer active.</returns>
bool CheckTimers (out int waitTimeout);
}
28 changes: 0 additions & 28 deletions Terminal.Gui/App/MainLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,32 +75,6 @@ public void Dispose ()
MainLoopDriver = null;
}

/// <summary>
/// Adds specified idle handler function to <see cref="MainLoop"/> processing. The handler function will be called
/// once per iteration of the main loop after other events have been handled.
/// </summary>
/// <remarks>
/// <para>Remove an idle handler by calling <see cref="TimedEvents.RemoveIdle(Func{bool})"/> with the token this method returns.</para>
/// <para>
/// If the <paramref name="idleHandler"/> returns <see langword="false"/> it will be removed and not called
/// subsequently.
/// </para>
/// </remarks>
/// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="TimedEvents.RemoveIdle(Func{bool})"/> .</param>
// QUESTION: Why are we re-inventing the event wheel here?
// PERF: This is heavy.
// CONCURRENCY: Race conditions exist here.
// CONCURRENCY: null delegates will hose this.
//
internal Func<bool> AddIdle (Func<bool> idleHandler)
{
TimedEvents.AddIdle (idleHandler);

MainLoopDriver?.Wakeup ();

return idleHandler;
}


/// <summary>Determines whether there are pending events to be processed.</summary>
/// <remarks>
Expand Down Expand Up @@ -139,8 +113,6 @@ internal void RunIteration ()
MainLoopDriver?.Iteration ();

TimedEvents.LockAndRunTimers ();

TimedEvents.LockAndRunIdles ();
}

private void RunAnsiScheduler ()
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/App/MainLoopSyncContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal sealed class MainLoopSyncContext : SynchronizationContext

public override void Post (SendOrPostCallback d, object state)
{
Application.MainLoop?.AddIdle (
Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero,
() =>
{
d (state);
Expand Down
86 changes: 2 additions & 84 deletions Terminal.Gui/App/TimedEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,15 @@ namespace Terminal.Gui.App;
/// </summary>
public class TimedEvents : ITimedEvents
{
internal List<Func<bool>> _idleHandlers = new ();
internal SortedList<long, Timeout> _timeouts = new ();

/// <summary>The idle handlers and lock that must be held while manipulating them</summary>
private readonly object _idleHandlersLock = new ();

private readonly object _timeoutsLockToken = new ();


/// <summary>Gets a copy of the list of all idle handlers.</summary>
public ReadOnlyCollection<Func<bool>> IdleHandlers
{
get
{
lock (_idleHandlersLock)
{
return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
}
}
}

/// <summary>
/// Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks. A shorter limit time can be
/// added at the end, but it will be called before an earlier addition that has a longer limit time.
/// </summary>
public SortedList<long, Timeout> Timeouts => _timeouts;

/// <inheritdoc />
public void AddIdle (Func<bool> idleHandler)
{
lock (_idleHandlersLock)
{
_idleHandlers.Add (idleHandler);
}
}

/// <inheritdoc/>
public event EventHandler<TimeoutEventArgs>? TimeoutAdded;

Expand Down Expand Up @@ -77,32 +50,6 @@ private long NudgeToUniqueKey (long k)
return k;
}


// PERF: This is heavier than it looks.
// CONCURRENCY: Potential deadlock city here.
// CONCURRENCY: Multiple concurrency pitfalls on the delegates themselves.
// INTENT: It looks like the general architecture here is trying to be a form of publisher/consumer pattern.
private void RunIdle ()
{
Func<bool> [] iterate;
lock (_idleHandlersLock)
{
iterate = _idleHandlers.ToArray ();
_idleHandlers = new List<Func<bool>> ();
}

foreach (Func<bool> idle in iterate)
{
if (idle ())
{
lock (_idleHandlersLock)
{
_idleHandlers.Add (idle);
}
}
}
}

/// <inheritdoc/>
public void LockAndRunTimers ()
{
Expand All @@ -116,21 +63,6 @@ public void LockAndRunTimers ()

}

/// <inheritdoc/>
public void LockAndRunIdles ()
{
bool runIdle;

lock (_idleHandlersLock)
{
runIdle = _idleHandlers.Count > 0;
}

if (runIdle)
{
RunIdle ();
}
}
private void RunTimers ()
{
long now = DateTime.UtcNow.Ticks;
Expand Down Expand Up @@ -165,15 +97,6 @@ private void RunTimers ()
}
}

/// <inheritdoc/>
public bool RemoveIdle (Func<bool> token)
{
lock (_idleHandlersLock)
{
return _idleHandlers.Remove (token);
}
}

/// <summary>Removes a previously scheduled timeout</summary>
/// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
/// Returns
Expand Down Expand Up @@ -219,7 +142,7 @@ public object AddTimeout (TimeSpan time, Func<bool> callback)
}

/// <inheritdoc/>
public bool CheckTimersAndIdleHandlers (out int waitTimeout)
public bool CheckTimers(out int waitTimeout)
{
long now = DateTime.UtcNow.Ticks;

Expand Down Expand Up @@ -247,11 +170,6 @@ public bool CheckTimersAndIdleHandlers (out int waitTimeout)
waitTimeout = -1;
}

// There are no timers set, check if there are any idle handlers

lock (_idleHandlersLock)
{
return _idleHandlers.Count > 0;
}
return false;
}
}
2 changes: 1 addition & 1 deletion Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ bool IMainLoopDriver.EventsPending ()

UpdatePollMap ();

bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int pollTimeout);
bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimers (out int pollTimeout);

int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout);

Expand Down
8 changes: 4 additions & 4 deletions Terminal.Gui/Drivers/EscSeqUtils/EscSeqUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ Action<MouseFlags, Point> continuousButtonPressedHandler

if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0)
{
Application.MainLoop?.AddIdle (
Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero,
() =>
{
// INTENT: What's this trying to do?
Expand Down Expand Up @@ -945,7 +945,7 @@ Action<MouseFlags, Point> continuousButtonPressedHandler
_isButtonClicked = false;
_isButtonDoubleClicked = true;

Application.MainLoop?.AddIdle (
Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero,
() =>
{
Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
Expand All @@ -959,7 +959,7 @@ Action<MouseFlags, Point> continuousButtonPressedHandler
// lastMouseButtonReleased = null;
// isButtonReleased = false;
// isButtonClicked = true;
// Application.MainLoop.AddIdle (() => {
// Application.MainLoop.AddTimeout (() => {
// Task.Run (async () => await ProcessButtonClickedAsync ());
// return false;
// });
Expand All @@ -984,7 +984,7 @@ Action<MouseFlags, Point> continuousButtonPressedHandler
mouseFlags.Add (GetButtonClicked (buttonState));
_isButtonClicked = true;

Application.MainLoop?.AddIdle (
Application.MainLoop?.TimedEvents.AddTimeout (TimeSpan.Zero,
() =>
{
Task.Run (async () => await ProcessButtonClickedAsync ());
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Drivers/NetDriver/NetMainLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ bool IMainLoopDriver.EventsPending ()

_waitForProbe.Set ();

if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout))
if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimers (out int waitTimeout))
{
return true;
}
Expand All @@ -84,7 +84,7 @@ bool IMainLoopDriver.EventsPending ()

if (!_eventReadyTokenSource.IsCancellationRequested)
{
return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _);
return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimers (out _);
}

// If cancellation was requested then always return true
Expand Down
Loading
Loading