Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring back iOS application delegates and fix many issues #6453

Merged
merged 5 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions SampleGame.iOS/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Foundation;
using osu.Framework;
using osu.Framework.iOS;

namespace SampleGame.iOS
{
[Register("AppDelegate")]
public class AppDelegate : GameApplicationDelegate
{
protected override Game CreateGame() => new SampleGameGame();
}
}
13 changes: 0 additions & 13 deletions SampleGame.iOS/Application.cs

This file was deleted.

15 changes: 15 additions & 0 deletions SampleGame.iOS/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using UIKit;

namespace SampleGame.iOS
{
public static class Program
{
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using osu.Framework.iOS;
using TemplateGame.Game;

namespace TemplateGame.iOS
{
/// <inheritdoc />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is [Register("AppDelegate")] missing here?

Why is it required in the first place?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attribute is inserted by default into every created iOS project, I did not delve deep into why it's required but I wouldn't want to defy what .NET/Xamarin does so /shrug. Will fix the missing specification.

public class AppDelegate : GameApplicationDelegate
{
/// <inheritdoc />
protected override osu.Framework.Game CreateGame() => new TemplateGameGame();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using UIKit;

namespace TemplateGame.iOS
{
/// <summary>
/// </summary>
public static class Program
{
/// <summary>
/// </summary>
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FlappyDon.Game;
using osu.Framework.iOS;

namespace FlappyDon.iOS
{
/// <inheritdoc />
public class AppDelegate : GameApplicationDelegate
{
/// <inheritdoc />
protected override osu.Framework.Game CreateGame() => new FlappyDonGame();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using UIKit;

namespace FlappyDon.iOS
{
/// <summary>
/// </summary>
public static class Program
{
/// <summary>
/// </summary>
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
14 changes: 14 additions & 0 deletions osu.Framework.Tests.iOS/AppDelegate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Foundation;
using osu.Framework.iOS;

namespace osu.Framework.Tests
{
[Register("AppDelegate")]
public class AppDelegate : GameApplicationDelegate
{
protected override Game CreateGame() => new VisualTestGame();
}
}
13 changes: 0 additions & 13 deletions osu.Framework.Tests.iOS/Application.cs

This file was deleted.

15 changes: 15 additions & 0 deletions osu.Framework.Tests.iOS/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using UIKit;

namespace osu.Framework.Tests
{
public static class Program
{
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,66 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using AVFoundation;
using Foundation;
using ManagedBass;
using ManagedBass.Fx;
using ManagedBass.Mix;
using SDL;
using UIKit;
using static SDL.SDL3;

namespace osu.Framework.iOS
{
public static class GameApplication
/// <summary>
/// Base <see cref="UIApplicationDelegate"/> implementation for osu!framework applications.
/// </summary>
public abstract class GameApplicationDelegate : UIApplicationDelegate
{
private const string output_volume = @"outputVolume";
internal event Action<string>? DragDrop;

private static IOSGameHost host = null!;
private static Game game = null!;
private const string output_volume = "outputVolume";

private static readonly OutputVolumeObserver output_volume_observer = new OutputVolumeObserver();

public static unsafe void Main(Game target)
{
NativeLibrary.SetDllImportResolver(typeof(Bass).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass.framework/bass", assembly, path));
NativeLibrary.SetDllImportResolver(typeof(BassFx).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass_fx.framework/bass_fx", assembly, path));
NativeLibrary.SetDllImportResolver(typeof(BassMix).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bassmix.framework/bassmix", assembly, path));
NativeLibrary.SetDllImportResolver(typeof(SDL3).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/SDL3.framework/SDL3", assembly, path));
private IOSGameHost host = null!;

game = target;
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
mapLibraryNames();

SDL3.SDL_RunApp(0, null, &main, IntPtr.Zero);
}
SDL_SetMainReady();
SDL_SetiOSEventPump(true);

[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static unsafe int main(int argc, byte** argv)
{
var audioSession = AVAudioSession.SharedInstance();
audioSession.AddObserver(output_volume_observer, output_volume, NSKeyValueObservingOptions.New, 0);

host = new IOSGameHost();
host.Run(game);
host.Run(CreateGame());
return true;
}

return 0;
public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
{
// copied verbatim from SDL: https://github.com/libsdl-org/SDL/blob/d252a8fe126b998bd1b0f4e4cf52312cd11de378/src/video/uikit/SDL_uikitappdelegate.m#L508-L535
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you subclass SDLUIKitDelegate? Seems to be supported by SDL. Maybe we don't want the extra baggage that comes from the SDL implementation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the hope at first but it was an extremely complicated process. We haven't explored through the concept of "iOS binding projects" before, which is an essential key to expose such types in the C# world. And throughout a brief attempt from my end, it cannot be integrated within the SDL3-CS project, it has to be in a separate project, either SDL3-CS.iOSBindings or osu.Framework.iOS.SDLBindings, depending on what the end goal with the iOS binding project turns out.

It will be a multi-day effort with back-and-fourth discussions on where and how should it be implemented and whether it's going to cause issues if it's a separate project (looping back to the concern about the Android project being previously separate), so I ultimately gave up on it on the trust that SDL will not silently add crucial code in SDLUIKitDelegate.

// the hope is that the SDL app delegate class does not have such handling exist there, but Apple does not provide a corresponding notification to make that possible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some copy-paste error in this comment? I don't really understand what it's supposed to say.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no error, there are just some iOS terminologies mixed in the comment. Apple provides things called Notifications in the iOS frameworks which allow applications to intercept events happening on any existing NSObject (i.e. any object), as long as said object exposes said Notifications (there's more into this but I don't want to dwell any further).

The key point of this comment is that some functionalities that used to exist in SDLUIKitDelegate were moved out to "observer" notification handlers to keep SDL away from the AppDelegate class. See libsdl-org/SDL#6011 (comment). However, there's no equivalent notification for handling OpenUrl outside of AppDelegate class, hence the existence of this comment and the copy-pasted code.

NSUrl? fileUrl = url.FilePathUrl;
DragDrop?.Invoke(fileUrl != null ? fileUrl.Path! : url.AbsoluteString!);
return true;
}

/// <summary>
/// Creates the <see cref="Game"/> class to launch.
/// </summary>
protected abstract Game CreateGame();

private static void mapLibraryNames()
{
NativeLibrary.SetDllImportResolver(typeof(Bass).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass.framework/bass", assembly, path));
NativeLibrary.SetDllImportResolver(typeof(BassFx).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bass_fx.framework/bass_fx", assembly, path));
NativeLibrary.SetDllImportResolver(typeof(BassMix).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/bassmix.framework/bassmix", assembly, path));
NativeLibrary.SetDllImportResolver(typeof(SDL3).Assembly, (_, assembly, path) => NativeLibrary.Load("@rpath/SDL3.framework/SDL3", assembly, path));
}

private class OutputVolumeObserver : NSObject
Expand Down
5 changes: 3 additions & 2 deletions osu.Framework.iOS/IOSWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public override void Create()

uiWindow = Runtime.GetNSObject<UIWindow>(WindowHandle);
updateSafeArea();

var appDelegate = (GameApplicationDelegate)UIApplication.SharedApplication.Delegate;
appDelegate.DragDrop += TriggerDragDrop;
}

protected override unsafe void RunMainLoop()
Expand All @@ -59,8 +62,6 @@ protected override unsafe void RunMainLoop()
// iOS may be a good forward direction if this ever comes up, as a user may see a potentially higher
// frame rate with multi-threaded mode turned on, but it is going to give them worse input latency
// and higher power usage.

SDL_SetiOSEventPump(false);
SDL_SetiOSAnimationCallback(SDLWindowHandle, 1, &runFrame, ObjectHandle.Handle);
}

Expand Down
2 changes: 2 additions & 0 deletions osu.Framework/Platform/SDL3/SDL3Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ internal virtual void SetIconFromGroup(IconGroup iconGroup)
/// </summary>
public event Action<string>? DragDrop;

protected void TriggerDragDrop(string filename) => DragDrop?.Invoke(filename);

#endregion

public void Dispose()
Expand Down
2 changes: 1 addition & 1 deletion osu.Framework/Platform/SDL3/SDL3Window_Input.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ private void handleDropEvent(SDL_DropEvent evtDrop)
case SDL_EventType.SDL_EVENT_DROP_FILE:
string? str = evtDrop.GetData();
if (str != null)
DragDrop?.Invoke(str);
TriggerDragDrop(str);

break;
}
Expand Down
Loading