From 2a5379fa2ad35e35f6c3b0e7c522a96d88a48036 Mon Sep 17 00:00:00 2001 From: Thomas Altenburger Date: Fri, 15 Dec 2023 21:24:43 +0100 Subject: [PATCH 1/3] Fix function loader ILC warning (#8105) --- MonoGame.Framework/Platform/Utilities/FuncLoader.Desktop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MonoGame.Framework/Platform/Utilities/FuncLoader.Desktop.cs b/MonoGame.Framework/Platform/Utilities/FuncLoader.Desktop.cs index 33af98aae3e..468b2ae84ad 100644 --- a/MonoGame.Framework/Platform/Utilities/FuncLoader.Desktop.cs +++ b/MonoGame.Framework/Platform/Utilities/FuncLoader.Desktop.cs @@ -38,7 +38,7 @@ private class OSX public static IntPtr LoadLibraryExt(string libname) { var ret = IntPtr.Zero; - var assemblyLocation = Path.GetDirectoryName(typeof(FuncLoader).Assembly.Location) ?? "./"; + var assemblyLocation = Path.GetDirectoryName(System.AppContext.BaseDirectory) ?? "./"; // Try .NET Framework / mono locations if (CurrentPlatform.OS == OS.MacOSX) From b30122c99597eaf81b81f32ab1d467a7b4185c73 Mon Sep 17 00:00:00 2001 From: eosxff Date: Sat, 16 Dec 2023 13:40:54 -0800 Subject: [PATCH 2/3] Fixed _isRunningOnNetCore bug in ContentTypeReaderManager.cs (#7895) --- MonoGame.Framework/Content/ContentTypeReaderManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MonoGame.Framework/Content/ContentTypeReaderManager.cs b/MonoGame.Framework/Content/ContentTypeReaderManager.cs index ec515dafda5..bbedf92fbd0 100644 --- a/MonoGame.Framework/Content/ContentTypeReaderManager.cs +++ b/MonoGame.Framework/Content/ContentTypeReaderManager.cs @@ -21,7 +21,7 @@ public sealed class ContentTypeReaderManager private static readonly string _assemblyName; - private static readonly bool _isRunningOnNetCore = Type.GetType("System.Private.CoreLib") != null; + private static readonly bool _isRunningOnNetCore = typeof(object).Assembly.GetName().Name == "System.Private.CoreLib"; static ContentTypeReaderManager() { @@ -51,7 +51,7 @@ internal ContentTypeReader[] LoadAssetReaders(ContentReader reader) // Trick to prevent the linker removing the code, but not actually execute the code if (falseflag) { - // Dummy variables required for it to work on iDevices ** DO NOT DELETE ** + // Dummy variables required for it to work on iDevices ** DO NOT DELETE ** // This forces the classes not to be optimized out when deploying to iDevices var hByteReader = new ByteReader(); var hSByteReader = new SByteReader(); @@ -152,7 +152,7 @@ internal ContentTypeReader[] LoadAssetReaders(ContentReader reader) catch (TargetInvocationException ex) { // If you are getting here, the Mono runtime is most likely not able to JIT the type. - // In particular, MonoTouch needs help instantiating types that are only defined in strings in Xnb files. + // In particular, MonoTouch needs help instantiating types that are only defined in strings in Xnb files. throw new InvalidOperationException( "Failed to get default constructor for ContentTypeReader. To work around, add a creation function to ContentTypeReaderManager.AddTypeCreator() " + "with the following failed type string: " + originalReaderTypeString, ex); @@ -198,7 +198,7 @@ internal ContentTypeReader[] LoadAssetReaders(ContentReader reader) /// /// /// Supports multiple generic types (e.g. Dictionary<TKey,TValue>) and nested generic types (e.g. List<List<int>>). - /// + /// /// /// A /// From 5fc1d043cce9d4a746f7950433f670e63c5dc407 Mon Sep 17 00:00:00 2001 From: Peru S Date: Mon, 18 Dec 2023 06:51:34 -0800 Subject: [PATCH 3/3] Add high-frequency touch events to iOS GameView (#8092) * Fix iOS GamePlatform to run pending background tasks #7520 In other `Platform` classes (such as `SDLGamePlatform` and `AndroidGamePlatform`, `Threading.Run()` is called during `Tick()` or `RunLoop()` (inconsistently, it may be called before or after `Game.Tick()` unfortunately). Fixing this for `iOSGamePlatform`. `WinForms` and perhaps other platforms (Switch?) may suffer from the same issue. Note that texture loading from background thread is discouraged however a lot of computationally intensive operations (reading/decoding the image file streams, allocating CPU buffers) could be done in the background thread and hence is a good practice to move such work alone into the bgthread until we request the actual texture from the main thread. * Update docs for sync/test code * Fix typo in comment * Add coalesced touch events to iOS GameView * Add coalesced touch API to TouchPanel* * Fix UTF-8 copyright mistake * Ensure we process coalesced touches only if the client asked for it * Avoid using C#5.0+ features (expression-bodied properties) * Suggested changes * Remove Coalesced API (new) and update existing touch event with a flag for `isHighFrequency`. - Also consistently use `high-frequency` instead of `coalesced` when referring to this new behavior. * Remove `out int` C# 7.0 / use C# 5.0 feature instead * Update based on @harry-cpp comments * Rename to `origin` instead of `upstream` * Remove git stuff * Address PR comments * Change (c) UTF-8 due to codepage issues, my bad. --------- Co-authored-by: Tom Spilman Co-authored-by: Thao D Co-authored-by: Simon (Darkside) Jackson Co-authored-by: mindfulplay.build --- MonoGame.Framework/IPlatformBackButton.cs | 2 +- .../Input/Touch/TouchLocation.cs | 29 ++- MonoGame.Framework/Input/Touch/TouchPanel.cs | 17 +- .../Input/Touch/TouchPanelState.cs | 35 +++- .../Platform/iOS/iOSGameView_Touch.cs | 190 +++++++++++------- 5 files changed, 188 insertions(+), 85 deletions(-) diff --git a/MonoGame.Framework/IPlatformBackButton.cs b/MonoGame.Framework/IPlatformBackButton.cs index 7aa1c600fb4..29ed08afddc 100644 --- a/MonoGame.Framework/IPlatformBackButton.cs +++ b/MonoGame.Framework/IPlatformBackButton.cs @@ -9,7 +9,7 @@ public interface IPlatformBackButton { /// /// Return true if your game has handled the back button event - /// retrn false if you want the operating system to handle it. + /// return false if you want the operating system to handle it. /// bool Handled(); } diff --git a/MonoGame.Framework/Input/Touch/TouchLocation.cs b/MonoGame.Framework/Input/Touch/TouchLocation.cs index 91aa8673713..29386248241 100644 --- a/MonoGame.Framework/Input/Touch/TouchLocation.cs +++ b/MonoGame.Framework/Input/Touch/TouchLocation.cs @@ -1,7 +1,7 @@ #region License // /* // Microsoft Public License (Ms-PL) -// MonoGame - Copyright © 2009-2010 The MonoGame Team +// MonoGame - Copyright (C) 2009-2010 The MonoGame Team // // All rights reserved. // @@ -63,6 +63,8 @@ public struct TouchLocation : IEquatable // Used for gesture recognition. private Vector2 _velocity; + // Use for high-frequency (optional) touch events processing + private bool _isHighFrequency; private Vector2 _pressPosition; private TimeSpan _pressTimestamp; private TimeSpan _timestamp; @@ -144,17 +146,22 @@ public TouchLocation(int id, TouchLocationState state, Vector2 position) public TouchLocation( int id, TouchLocationState state, Vector2 position, TouchLocationState previousState, Vector2 previousPosition) - : this(id, state, position, previousState, previousPosition, TimeSpan.Zero) + : this(id, state, position, previousState, previousPosition, TimeSpan.Zero, false) { } internal TouchLocation(int id, TouchLocationState state, Vector2 position, TimeSpan timestamp) - : this(id, state, position, TouchLocationState.Invalid, Vector2.Zero, timestamp) + : this(id, state, position, TouchLocationState.Invalid, Vector2.Zero, timestamp, false) + { + } + + internal TouchLocation(int id, TouchLocationState state, Vector2 position, TimeSpan timestamp, bool isHighFrequency) + : this(id, state, position, TouchLocationState.Invalid, Vector2.Zero, timestamp, isHighFrequency) { } internal TouchLocation(int id, TouchLocationState state, Vector2 position, - TouchLocationState previousState, Vector2 previousPosition, TimeSpan timestamp) + TouchLocationState previousState, Vector2 previousPosition, TimeSpan timestamp, bool isHighFrequency) { _id = id; _state = state; @@ -168,6 +175,8 @@ internal TouchLocation(int id, TouchLocationState state, Vector2 position, TimeS _timestamp = timestamp; _velocity = Vector2.Zero; + _isHighFrequency = isHighFrequency; + // If this is a pressed location then store the // current position and timestamp as pressed. if (state == TouchLocationState.Pressed) @@ -253,6 +262,14 @@ internal bool UpdateState(TouchLocation touchEvent) return _state != _previousState || delta.LengthSquared() > 0.001f; } + /// + /// Returns true if the touch panel is configured to process high frequence touch events. + /// + public bool IsHighFrequencyEvent() + { + return _isHighFrequency; + } + public override bool Equals(object obj) { if (obj is TouchLocation) @@ -268,6 +285,8 @@ public bool Equals(TouchLocation other) _previousPosition.Equals(other._previousPosition); } + + public override int GetHashCode() { return _id; @@ -294,6 +313,7 @@ public bool TryGetPreviousLocation(out TouchLocation aPreviousLocation) aPreviousLocation._pressTimestamp = TimeSpan.Zero; aPreviousLocation._velocity = Vector2.Zero; aPreviousLocation.SameFrameReleased = false; + aPreviousLocation._isHighFrequency = false; return false; } @@ -309,6 +329,7 @@ public bool TryGetPreviousLocation(out TouchLocation aPreviousLocation) aPreviousLocation._pressTimestamp = _pressTimestamp; aPreviousLocation._velocity = _velocity; aPreviousLocation.SameFrameReleased = SameFrameReleased; + aPreviousLocation._isHighFrequency = _isHighFrequency; return true; } diff --git a/MonoGame.Framework/Input/Touch/TouchPanel.cs b/MonoGame.Framework/Input/Touch/TouchPanel.cs index 27cf2866f26..df1b62c1e02 100644 --- a/MonoGame.Framework/Input/Touch/TouchPanel.cs +++ b/MonoGame.Framework/Input/Touch/TouchPanel.cs @@ -1,7 +1,7 @@ #region License // /* // Microsoft Public License (Ms-PL) -// XnaTouch - Copyright © 2009-2010 The XnaTouch Team +// XnaTouch - Copyright (C) 2009-2010 The XnaTouch Team // // All rights reserved. // @@ -68,6 +68,11 @@ public static TouchPanelCapabilities GetCapabilities() return PrimaryWindow.TouchPanelState.GetCapabilities(); } + internal static void AddHighResolutionTouchEvent(int id, TouchLocationState state, Vector2 position) + { + PrimaryWindow.TouchPanelState.AddHighResolutionTouchEvent(id, state, position); + } + internal static void AddEvent(int id, TouchLocationState state, Vector2 position) { AddEvent(id, state, position, false); @@ -152,5 +157,15 @@ public static bool IsGestureAvailable { get { return PrimaryWindow.TouchPanelState.IsGestureAvailable; } } + + /// + /// Gets or sets if high-frequency events are sent to any listeners (if the underlying OS supports it). + /// By default, it's and hence no additional CPU usage is incurred. + /// + public static bool EnableHighFrequencyTouch + { + get { return PrimaryWindow.TouchPanelState.EnableHighFrequencyTouch; } + set { PrimaryWindow.TouchPanelState.EnableHighFrequencyTouch = value; } + } } } \ No newline at end of file diff --git a/MonoGame.Framework/Input/Touch/TouchPanelState.cs b/MonoGame.Framework/Input/Touch/TouchPanelState.cs index 27873618391..4369ac31aeb 100644 --- a/MonoGame.Framework/Input/Touch/TouchPanelState.cs +++ b/MonoGame.Framework/Input/Touch/TouchPanelState.cs @@ -55,6 +55,13 @@ public class TouchPanelState private TouchPanelCapabilities Capabilities = new TouchPanelCapabilities(); + + /// + /// Raised when a new touch event is processed. Clients can use this + /// to obtain per-frame touch events beyond the scope of gestures/touch panel. + /// + public event EventHandler OnTouchEvent; + internal readonly GameWindow Window; internal TouchPanelState(GameWindow window) @@ -153,6 +160,19 @@ public TouchCollection GetState() return result; } + internal void AddHighResolutionTouchEvent(int id, TouchLocationState state, Vector2 position) + { + //Try to find the touch id. + int touchId; + if (!_touchIds.TryGetValue(id, out touchId)) + { + return; + } + var evt = new TouchLocation(touchId, state, position * _touchScale, CurrentTimestamp, + /* isHighFrequencyEvent */ true); + EventHelpers.Raise(this, OnTouchEvent, evt); + } + internal void AddEvent(int id, TouchLocationState state, Vector2 position) { AddEvent(id, state, position, false); @@ -191,7 +211,8 @@ internal void AddEvent(int id, TouchLocationState state, Vector2 position, bool { // If we got here that means either the device is sending // us bad, out of order, or old touch events. In any case - // just ignore them. + // just ignore them (but release existing ones, so we are + // not trapped forever in a bad state). return; } @@ -200,14 +221,16 @@ internal void AddEvent(int id, TouchLocationState state, Vector2 position, bool // Add the new touch event keeping the list from getting // too large if no one happens to be requesting the state. var evt = new TouchLocation(touchId, state, position * _touchScale, CurrentTimestamp); + EventHelpers.Raise(this, OnTouchEvent, evt); if (!isMouse || EnableMouseTouchPoint) { ApplyTouch(_touchState, evt); } - //If we have gestures enabled then collect events for those too. - //We also have to keep tracking any touches while we know about touches so we don't miss releases even if gesture recognition is disabled + // If we have gestures enabled then collect events for those too. + // We also have to keep tracking any touches while we know about touches + // so we don't miss releases even if gesture recognition is disabled if ((EnabledGestures != GestureType.None || _gestureState.Count > 0) && (!isMouse || EnableMouseGestures)) { ApplyTouch(_gestureState, evt); @@ -312,6 +335,12 @@ public int DisplayWidth public bool EnableMouseGestures { get; set; } + /// + /// Gets or sets if high-frequency touch event processing is enabled. + /// See for more details. + /// + public bool EnableHighFrequencyTouch { get; set; } + /// /// Returns true if a touch gesture is available. /// diff --git a/MonoGame.Framework/Platform/iOS/iOSGameView_Touch.cs b/MonoGame.Framework/Platform/iOS/iOSGameView_Touch.cs index 0ae69cf2b27..719357bb0d2 100644 --- a/MonoGame.Framework/Platform/iOS/iOSGameView_Touch.cs +++ b/MonoGame.Framework/Platform/iOS/iOSGameView_Touch.cs @@ -77,84 +77,122 @@ 1. Definitions using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; -namespace Microsoft.Xna.Framework { - partial class iOSGameView { - - static GestureType EnabledGestures - { - get { return TouchPanel.EnabledGestures; } - } - - #region Touches - - public override void TouchesBegan (NSSet touches, UIEvent evt) - { - base.TouchesBegan (touches, evt); - FillTouchCollection (touches); - } - - public override void TouchesEnded (NSSet touches, UIEvent evt) - { - base.TouchesEnded (touches, evt); - FillTouchCollection (touches); - } - - public override void TouchesMoved (NSSet touches, UIEvent evt) - { - base.TouchesMoved (touches, evt); - FillTouchCollection (touches); - } - - public override void TouchesCancelled (NSSet touches, UIEvent evt) - { - base.TouchesCancelled (touches, evt); - FillTouchCollection (touches); - } - - // TODO: Review FillTouchCollection - private void FillTouchCollection (NSSet touches) - { - if (touches.Count == 0) - return; - - var touchesArray = touches.ToArray (); - for (int i = 0; i < touchesArray.Length; ++i) { - var touch = touchesArray [i]; - - //Get position touch - var location = touch.LocationInView (touch.View); - var position = GetOffsetPosition (new Vector2 ((float)location.X, (float)location.Y), true); - var id = touch.Handle.GetHashCode(); - - switch (touch.Phase) +namespace Microsoft.Xna.Framework +{ + partial class iOSGameView + { + + static GestureType EnabledGestures + { + get { return TouchPanel.EnabledGestures; } + } + + #region Touches + + public override void TouchesBegan(NSSet touches, UIEvent evt) + { + base.TouchesBegan(touches, evt); + FillTouchCollection(touches, evt); + } + + public override void TouchesEnded(NSSet touches, UIEvent evt) + { + base.TouchesEnded(touches, evt); + FillTouchCollection(touches, evt); + } + + public override void TouchesMoved(NSSet touches, UIEvent evt) + { + base.TouchesMoved(touches, evt); + FillTouchCollection(touches, evt); + } + + public override void TouchesCancelled(NSSet touches, UIEvent evt) + { + base.TouchesCancelled(touches, evt); + FillTouchCollection(touches, evt); + } + + // Process and fill touch events: process high-frequency events if they are available, otherwise, + // use the last-frame touch events. + private void FillTouchCollection(NSSet touches, UIEvent evt) + { + if (touches.Count == 0) + return; + + var touchesArray = touches.ToArray(); + for (int touchIndex = 0; touchIndex < touchesArray.Length; ++touchIndex) + { + var touch = touchesArray[touchIndex]; + var id = touch.Handle.GetHashCode(); + FillTouch(touch, id, false); + + if (TouchPanel.EnableHighFrequencyTouch) { - //case UITouchPhase.Stationary: - case UITouchPhase.Moved: - TouchPanel.AddEvent(id, TouchLocationState.Moved, position); - break; - case UITouchPhase.Began: + var coalescedTouches = evt.GetCoalescedTouches(touch); + if (coalescedTouches != null) + { + // Per the document https://developer.apple.com/documentation/uikit/uievent/1613808-coalescedtouches, + // there may be a few coalesced touch events between two subsequence frames. The frequence of these + // events is perhaps more than the display frequency, and perhaps max out at 240Hz for Apple Pencil + // and so on. + for (int coalescedIndex = 0; coalescedIndex < coalescedTouches.Length; ++coalescedIndex) + { + FillTouch(coalescedTouches[coalescedIndex], id, true); + } + } + } + } + } + + private void FillTouch(UITouch touch, int id, bool coalesced) + { + //Get position touch + var location = touch.LocationInView(touch.View); + var position = GetOffsetPosition(new Vector2((float)location.X, (float)location.Y), true); + switch (touch.Phase) + { + case UITouchPhase.Stationary: + if (coalesced) + TouchPanel.AddHighResolutionTouchEvent(id, TouchLocationState.Moved, position); + break; + case UITouchPhase.Moved: + if (coalesced) + TouchPanel.AddHighResolutionTouchEvent(id, TouchLocationState.Moved, position); + else + TouchPanel.AddEvent(id, TouchLocationState.Moved, position); + break; + case UITouchPhase.Began: + if (coalesced) + TouchPanel.AddHighResolutionTouchEvent(id, TouchLocationState.Pressed, position); + else TouchPanel.AddEvent(id, TouchLocationState.Pressed, position); - break; - case UITouchPhase.Ended : + break; + case UITouchPhase.Ended: + if (coalesced) + TouchPanel.AddHighResolutionTouchEvent(id, TouchLocationState.Released, position); + else TouchPanel.AddEvent(id, TouchLocationState.Released, position); - break; - case UITouchPhase.Cancelled: + break; + case UITouchPhase.Cancelled: + if (coalesced) + TouchPanel.AddHighResolutionTouchEvent(id, TouchLocationState.Released, position); + else TouchPanel.AddEvent(id, TouchLocationState.Released, position); - break; - default: - break; - } - } - } - - // TODO: Review GetOffsetPosition, hopefully it can be removed now. - public Vector2 GetOffsetPosition (Vector2 position, bool useScale) - { - if (useScale) - return position * (float)Layer.ContentsScale; - return position; - } - - #endregion Touches - } + break; + default: + break; + } + } + + // UI touch events are returned in content space while MonoGame uses screen-space pixel coordinates. + public Vector2 GetOffsetPosition(Vector2 position, bool useScale) + { + if (useScale) + return position * (float)Layer.ContentsScale; + return position; + } + + #endregion Touches + } }