Refactor iOS deployment to use XCode project

This commit is contained in:
Wojtek Figat
2023-06-01 00:46:39 +02:00
parent 5416d385d3
commit c46f78885e
22 changed files with 867 additions and 403 deletions

View File

@@ -5,9 +5,11 @@
#include "iOSPlatformTools.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Platform/iOS/iOSPlatformSettings.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Engine/Core/Config/BuildSettings.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Content.h"
@@ -119,11 +121,10 @@ bool iOSPlatformTools::IsNativeCodeFile(CookingData& data, const String& file)
void iOSPlatformTools::OnBuildStarted(CookingData& data)
{
// Adjust the cooking output folders for packaging app
const auto appName = GetAppName();
String contents = String(TEXT("/Payload/")) / appName + TEXT(".app/");
data.DataOutputPath /= contents;
data.NativeCodeOutputPath /= contents;
data.ManagedCodeOutputPath /= contents;
const Char* subDir = TEXT("FlaxGame/Data");
data.DataOutputPath /= subDir;
data.NativeCodeOutputPath /= subDir;
data.ManagedCodeOutputPath /= subDir;
PlatformTools::OnBuildStarted(data);
}
@@ -166,141 +167,85 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
}
}
// Find executable
String executableName;
// Copy fresh Gradle project template
if (FileSystem::CopyDirectory(data.OriginalOutputPath, platformDataPath / TEXT("Project"), true))
{
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
for (auto& file : files)
{
if (FileSystem::GetExtension(file).IsEmpty())
{
executableName = StringUtils::GetFileName(file);
break;
}
}
}
// Deploy app icon
TextureData iconData;
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
{
String tmpFolderPath = data.DataOutputPath / TEXT("icon.iconset");
if (!FileSystem::DirectoryExists(tmpFolderPath))
FileSystem::CreateDirectory(tmpFolderPath);
String srcIconPath = tmpFolderPath / TEXT("icon_1024x1024.png");
if (EditorUtilities::ExportApplicationImage(iconData, 1024, 1024, PixelFormat::R8G8B8A8_UNorm, srcIconPath))
{
LOG(Error, "Failed to export application icon.");
return true;
}
bool failed = Platform::RunProcess(TEXT("sips -z 16 16 icon_1024x1024.png --out icon_16x16.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_16x16@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 32 32 icon_1024x1024.png --out icon_32x32.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 64 64 icon_1024x1024.png --out icon_32x32@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 128 128 icon_1024x1024.png --out icon_128x128.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_128x128@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 256 256 icon_1024x1024.png --out icon_256x256.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_256x256@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 512 512 icon_1024x1024.png --out icon_512x512.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("sips -z 1024 1024 icon_1024x1024.png --out icon_512x512@2x.png"), tmpFolderPath);
failed |= Platform::RunProcess(TEXT("iconutil -c icns icon.iconset"), data.DataOutputPath);
if (failed)
{
LOG(Error, "Failed to export application icon.");
return true;
}
FileSystem::DeleteDirectory(tmpFolderPath);
String iTunesArtworkPath = data.OriginalOutputPath / TEXT("iTunesArtwork.png");
if (EditorUtilities::ExportApplicationImage(iconData, 512, 512, PixelFormat::R8G8B8A8_UNorm, iTunesArtworkPath))
{
LOG(Error, "Failed to export application icon.");
return true;
}
FileSystem::MoveFile(data.OriginalOutputPath / TEXT("iTunesArtwork"), iTunesArtworkPath, true);
}
// Create PkgInfo file
const String pkgInfoPath = data.DataOutputPath / TEXT("PkgInfo");
File::WriteAllText(pkgInfoPath, TEXT("APPL???"), Encoding::ANSI);
// Create Info.plist file with package description
const String plistPath = data.DataOutputPath / TEXT("Info.plist");
{
xml_document doc;
xml_node plist = doc.child_or_append(PUGIXML_TEXT("plist"));
plist.append_attribute(PUGIXML_TEXT("version")).set_value(PUGIXML_TEXT("1.0"));
xml_node dict = plist.child_or_append(PUGIXML_TEXT("dict"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSRequiresIPhoneOS"));
dict.append_child(PUGIXML_TEXT("true"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("UIStatusBarHidden"));
dict.append_child(PUGIXML_TEXT("true"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("UIRequiresFullScreen"));
dict.append_child(PUGIXML_TEXT("true"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("UIViewControllerBasedStatusBarAppearance"));
dict.append_child(PUGIXML_TEXT("false"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("CFBundleIcons"));
dict.append_child(PUGIXML_TEXT("dict"));
#define ADD_ENTRY(key, value) \
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
dict.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT(value))
#define ADD_ENTRY_STR(key, value) \
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT(key)); \
{ std::u16string valueStr(value.GetText()); \
dict.append_child(PUGIXML_TEXT("string")).set_child_value(pugi::string_t(valueStr.begin(), valueStr.end()).c_str()); }
ADD_ENTRY("CFBundleDevelopmentRegion", "English");
ADD_ENTRY("CFBundlePackageType", "APPL");
ADD_ENTRY("CFBundleSignature", "????");
ADD_ENTRY("LSApplicationCategoryType", "public.app-category.games");
ADD_ENTRY("CFBundleInfoDictionaryVersion", "6.0");
ADD_ENTRY("CFBundleIconFile", "icon.icns");
ADD_ENTRY("MinimumOSVersion", "14.0");
ADD_ENTRY_STR("CFBundleExecutable", executableName);
ADD_ENTRY_STR("CFBundleIdentifier", appIdentifier);
ADD_ENTRY_STR("CFBundleName", appName); // TODO: limit to 15 chars max
ADD_ENTRY_STR("CFBundleDisplayName", gameSettings->ProductName);
ADD_ENTRY_STR("CFBundleGetInfoString", gameSettings->ProductName);
ADD_ENTRY_STR("CFBundleVersion", projectVersion);
ADD_ENTRY_STR("CFBundleShortVersionString", projectVersion);
ADD_ENTRY_STR("NSHumanReadableCopyright", gameSettings->CopyrightNotice);
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("UIRequiredDeviceCapabilities"));
xml_node UIRequiredDeviceCapabilities = dict.append_child(PUGIXML_TEXT("array"));
UIRequiredDeviceCapabilities.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("arm64"));
UIRequiredDeviceCapabilities.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("metal"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("UISupportedInterfaceOrientations"));
xml_node UISupportedInterfaceOrientations = dict.append_child(PUGIXML_TEXT("array"));
UISupportedInterfaceOrientations.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("UIInterfaceOrientationPortrait"));
UISupportedInterfaceOrientations.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("UIInterfaceOrientationLandscapeLeft"));
UISupportedInterfaceOrientations.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("UIInterfaceOrientationLandscapeRight"));
dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("CFBundleSupportedPlatforms"));
xml_node CFBundleSupportedPlatforms = dict.append_child(PUGIXML_TEXT("array"));
CFBundleSupportedPlatforms.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("iPhoneOS"));
#undef ADD_ENTRY
#undef ADD_ENTRY_STR
if (!doc.save_file(*StringAnsi(plistPath)))
{
LOG(Error, "Failed to save {0}", plistPath);
return true;
}
}
// TODO: expose event to inject custom post-processing before app packaging (eg. third-party plugins)
// Run code signing for Apple platform
if (EditorUtilities::CodeSignApple(data.DataOutputPath, GetPlatform(), *platformSettings))
LOG(Error, "Failed to deploy XCode project to {0} from {1}", data.OriginalOutputPath, platformDataPath);
return true;
}
// Format project template files
Dictionary<String, String> configReplaceMap;
configReplaceMap[TEXT("${AppName}")] = appName;
configReplaceMap[TEXT("${AppIdentifier}")] = appIdentifier;
configReplaceMap[TEXT("${AppTeamId}")] = platformSettings->AppTeamId;
configReplaceMap[TEXT("${AppVersion}")] = TEXT("1"); // TODO: expose to iOS platform settings (matches CURRENT_PROJECT_VERSION in XCode)
configReplaceMap[TEXT("${ProjectName}")] = gameSettings->ProductName;
configReplaceMap[TEXT("${ProjectVersion}")] = projectVersion;
configReplaceMap[TEXT("${HeaderSearchPaths}")] = Globals::StartupFolder;
// TODO: screen rotation settings in XCode project from iOS Platform Settings
{
// Initialize auto-generated areas as empty
configReplaceMap[TEXT("${PBXBuildFile}")] = String::Empty;
configReplaceMap[TEXT("${PBXCopyFilesBuildPhaseFiles}")] = String::Empty;
configReplaceMap[TEXT("${PBXFileReference}")] = String::Empty;
configReplaceMap[TEXT("${PBXFrameworksBuildPhase}")] = String::Empty;
configReplaceMap[TEXT("${PBXFrameworksGroup}")] = String::Empty;
configReplaceMap[TEXT("${PBXFilesGroup}")] = String::Empty;
configReplaceMap[TEXT("${PBXResourcesGroup}")] = String::Empty;
}
{
// Rename dotnet license files to not mislead the actual game licensing
FileSystem::MoveFile(data.DataOutputPath / TEXT("Dotnet/DOTNET-LICENSE.TXT"), data.DataOutputPath / TEXT("Dotnet/LICENSE.TXT"), true);
FileSystem::MoveFile(data.DataOutputPath / TEXT("Dotnet/DOTNET-THIRD-PARTY-NOTICES.TXT"), data.DataOutputPath / TEXT("Dotnet/THIRD-PARTY-NOTICES.TXT"), true);
}
Array<String> files;
FileSystem::DirectoryGetFiles(files, data.DataOutputPath, TEXT("*"), DirectorySearchOption::AllDirectories);
for (const String& file : files)
{
String name = StringUtils::GetFileName(file);
if (name == TEXT(".DS_Store") || name == TEXT("FlaxGame"))
continue;
String fileId = Guid::New().ToString(Guid::FormatType::N).Left(24);
String projectPath = FileSystem::ConvertAbsolutePathToRelative(data.DataOutputPath, file);
if (name.EndsWith(TEXT(".dylib")))
{
String frameworkId = Guid::New().ToString(Guid::FormatType::N).Left(24);
String frameworkEmbedId = Guid::New().ToString(Guid::FormatType::N).Left(24);
configReplaceMap[TEXT("${PBXBuildFile}")] += String::Format(TEXT("\t\t{0} /* {1} in Frameworks */ = {{isa = PBXBuildFile; fileRef = {2} /* {1} */; }};\n"), frameworkId, name, fileId);
configReplaceMap[TEXT("${PBXBuildFile}")] += String::Format(TEXT("\t\t{0} /* {1} in Embed Frameworks */ = {{isa = PBXBuildFile; fileRef = {2} /* {1} */; settings = {{ATTRIBUTES = (CodeSignOnCopy, ); }}; }};\n"), frameworkEmbedId, name, fileId);
configReplaceMap[TEXT("${PBXCopyFilesBuildPhaseFiles}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} in Embed Frameworks */,\n"), frameworkEmbedId, name);
configReplaceMap[TEXT("${PBXFileReference}")] += String::Format(TEXT("\t\t{0} /* {1} */ = {{isa = PBXFileReference; lastKnownFileType = \"compiled.mach-o.dylib\"; name = \"{1}\"; path = \"FlaxGame/Data/{2}\"; sourceTree = \"<group>\"; }};\n"), fileId, name, projectPath);
configReplaceMap[TEXT("${PBXFrameworksBuildPhase}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} in Frameworks */,\n"), frameworkId, name);
configReplaceMap[TEXT("${PBXFrameworksGroup}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} */,\n"), fileId, name);
// Fix rpath id
// TODO: run this only for dylibs during AOT process (other libs are fine)
CreateProcessSettings proc;
proc.FileName = TEXT("install_name_tool");
proc.Arguments = String::Format(TEXT("-id \"@rpath/{0}\" \"{1}\""), name, file);
Platform::CreateProcess(proc);
}
else
{
String fileRefId = Guid::New().ToString(Guid::FormatType::N).Left(24);
configReplaceMap[TEXT("${PBXBuildFile}")] += String::Format(TEXT("\t\t{0} /* {1} in Resources */ = {{isa = PBXBuildFile; fileRef = {2} /* {1} */; }};\n"), fileRefId, name, fileId);
configReplaceMap[TEXT("${PBXFileReference}")] += String::Format(TEXT("\t\t{0} /* {1} */ = {{isa = PBXFileReference; lastKnownFileType = file; name = \"{1}\"; path = \"Data/{2}\"; sourceTree = \"<group>\"; }};\n"), fileId, name, projectPath);
configReplaceMap[TEXT("${PBXFilesGroup}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} */,\n"), fileId, name);
configReplaceMap[TEXT("${PBXResourcesGroup}")] += String::Format(TEXT("\t\t\t\t{0} /* {1} in Resources */,\n"), fileRefId, name);
}
}
bool failed = false;
failed |= EditorUtilities::ReplaceInFile(data.OriginalOutputPath / TEXT("FlaxGame.xcodeproj/project.pbxproj"), configReplaceMap);
if (failed)
{
LOG(Error, "Failed to format XCode project");
return true;
}
// TODO: update splash screen images
// TODO: update game icon
// Package application
const auto buildSettings = BuildSettings::Get();
@@ -308,6 +253,8 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
return false;
GameCooker::PackageFiles();
LOG(Info, "Building app package...");
// TODO: run XCode archive and export
#if 0
const String ipaPath = data.OriginalOutputPath / appName + TEXT(".ipa");
const String ipaCommand = String::Format(TEXT("zip -r -X {0}.ipa Payload iTunesArtwork"), appName);
const int32 result = Platform::RunProcess(ipaCommand, data.OriginalOutputPath);
@@ -317,6 +264,7 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data)
return true;
}
LOG(Info, "Output application package: {0} (size: {1} MB)", ipaPath, FileSystem::GetFileSize(ipaPath) / 1024 / 1024);
#endif
return false;
}

View File

@@ -866,6 +866,16 @@ bool EditorUtilities::ReplaceInFile(const StringView& file, const StringView& fi
return File::WriteAllText(file, text, Encoding::ANSI);
}
bool EditorUtilities::ReplaceInFile(const StringView& file, const Dictionary<String, String, HeapAllocation>& replaceMap)
{
String text;
if (File::ReadAllText(file, text))
return true;
for (const auto& e : replaceMap)
text.Replace(e.Key.Get(), e.Key.Length(), e.Value.Get(), e.Value.Length());
return File::WriteAllText(file, text, Encoding::ANSI);
}
bool EditorUtilities::CopyFileIfNewer(const StringView& dst, const StringView& src)
{
if (FileSystem::FileExists(dst) &&

View File

@@ -85,6 +85,7 @@ public:
/// <param name="replaceWith">The value to replace to.</param>
/// <returns>True if failed, otherwise false.</returns>
static bool ReplaceInFile(const StringView& file, const StringView& findWhat, const StringView& replaceWith);
static bool ReplaceInFile(const StringView& file, const Dictionary<String, String, HeapAllocation>& replaceMap);
static bool CopyFileIfNewer(const StringView& dst, const StringView& src);
static bool CopyDirectoryIfNewer(const StringView& dst, const StringView& src, bool withSubDirectories);

View File

@@ -1679,7 +1679,7 @@ bool GPUDeviceVulkan::Init()
auto& limits = Limits;
limits.HasCompute = GetShaderProfile() == ShaderProfile::Vulkan_SM5 && PhysicalDeviceLimits.maxComputeWorkGroupCount[0] >= GPU_MAX_CS_DISPATCH_THREAD_GROUPS && PhysicalDeviceLimits.maxComputeWorkGroupCount[1] >= GPU_MAX_CS_DISPATCH_THREAD_GROUPS;
limits.HasTessellation = !!PhysicalDeviceFeatures.tessellationShader && PhysicalDeviceLimits.maxBoundDescriptorSets > (uint32_t)DescriptorSet::Domain;
#if PLATFORM_ANDROID
#if PLATFORM_ANDROID || PLATFORM_IOS
limits.HasGeometryShaders = false; // Don't even try GS on mobile
#else
limits.HasGeometryShaders = !!PhysicalDeviceFeatures.geometryShader;

View File

@@ -79,7 +79,7 @@ void GPUSwapChainVulkan::OnReleaseGPU()
bool GPUSwapChainVulkan::IsFullscreen()
{
#if PLATFORM_ANDROID || PLATFORM_SWITCH
#if PLATFORM_ANDROID || PLATFORM_SWITCH || PLATFORM_IOS
// Not supported
return true;
#else
@@ -89,7 +89,7 @@ bool GPUSwapChainVulkan::IsFullscreen()
void GPUSwapChainVulkan::SetFullscreen(bool isFullscreen)
{
#if PLATFORM_ANDROID || PLATFORM_SWITCH
#if PLATFORM_ANDROID || PLATFORM_SWITCH || PLATFORM_IOS
// Not supported
#else
if (!_surface)

View File

@@ -4,6 +4,7 @@
#include "iOSVulkanPlatform.h"
#include "../RenderToolsVulkan.h"
#include "Engine/Core/Delegate.h"
#include <UIKit/UIKit.h>
void iOSVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers)
@@ -14,11 +15,15 @@ void iOSVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Ar
void iOSVulkanPlatform::CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* surface)
{
MISSING_CODE("TODO: Vulkan via MoltenVK on iOS");
VkIOSSurfaceCreateInfoMVK surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK);
surfaceCreateInfo.pView = nullptr; // UIView or CAMetalLayer
VALIDATE_VULKAN_RESULT(vkCreateIOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
// Create surface on a main UI Thread
Function<void()> func = [&windowHandle, &instance, &surface]()
{
VkIOSSurfaceCreateInfoMVK surfaceCreateInfo;
RenderToolsVulkan::ZeroStruct(surfaceCreateInfo, VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK);
surfaceCreateInfo.pView = windowHandle; // UIView or CAMetalLayer
VALIDATE_VULKAN_RESULT(vkCreateIOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, surface));
};
iOSPlatform::RunOnUIThread(func, true);
}
#endif

View File

@@ -51,7 +51,6 @@
Guid DeviceId;
String ComputerName;
NSAutoreleasePool* AutoreleasePool = nullptr;
DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
{
@@ -232,7 +231,6 @@ bool MacPlatform::Init()
// Init application
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
AutoreleasePool = [[NSAutoreleasePool alloc] init];
// Init main menu
NSMenu* mainMenu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
@@ -276,8 +274,7 @@ void MacPlatform::Tick()
[NSApp sendEvent:event];
}
[AutoreleasePool drain];
AutoreleasePool = [[NSAutoreleasePool alloc] init];
ApplePlatform::Tick();
}
int32 MacPlatform::GetDpi()

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#pragma once
#ifndef PLATFORM_IOS
// When included from generated XCode main.m file
#define PLATFORM_IOS 1
#define FLAXENGINE_API
#endif
#if PLATFORM_IOS
#import <UIKit/UIKit.h>
FLAXENGINE_API
@interface FlaxView : UIView
@end
FLAXENGINE_API
@interface FlaxViewController : UIViewController
@end
FLAXENGINE_API
@interface FlaxAppDelegate : UIResponder <UIApplicationDelegate>
@property(strong, retain, nonatomic) UIWindow* window;
@property(strong, retain, nonatomic) FlaxViewController* viewController;
@property(strong, retain, nonatomic) FlaxView* view;
@end
#endif

View File

@@ -12,6 +12,7 @@
class FLAXENGINE_API iOSFileSystem : public AppleFileSystem
{
public:
// [AppleFileSystem]
static bool FileExists(const StringView& path);
static uint64 GetFileSize(const StringView& path);
static bool IsReadOnly(const StringView& path);

View File

@@ -6,7 +6,9 @@
#include "iOSWindow.h"
#include "iOSFile.h"
#include "iOSFileSystem.h"
#include "iOSApp.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Platform/Apple/AppleUtils.h"
@@ -15,20 +17,158 @@
#include "Engine/Platform/Window.h"
#include "Engine/Platform/WindowsManager.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Input/Input.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/Globals.h"
#include <UIKit/UIKit.h>
#include <CoreFoundation/CoreFoundation.h>
#include <QuartzCore/CAMetalLayer.h>
#include <sys/utsname.h>
int32 Dpi = 96;
Guid DeviceId;
FlaxView* MainView = nullptr;
FlaxViewController* MainViewController = nullptr;
iOSWindow* MainWindow = nullptr;
CriticalSection UIThreadFuncLocker;
Array<Function<void()>> UIThreadFuncList;
// Used by iOS project in XCode to run engine (see main.m)
extern "C" FLAXENGINE_API int FlaxEngineMain()
@implementation FlaxView
+(Class) layerClass { return [CAMetalLayer class]; }
- (void)setFrame:(CGRect)frame
{
return Engine::Main(TEXT(""));
[super setFrame:frame];
if (!MainWindow)
return;
float scale = [[UIScreen mainScreen] scale];
MainWindow->CheckForResize((float)frame.size.width * scale, (float)frame.size.height * scale);
}
@end
@implementation FlaxViewController
- (BOOL)prefersHomeIndicatorAutoHidden
{
return YES;
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return UIStatusBarAnimationSlide;
}
- (void)loadView
{
[super loadView];
UILabel *label = [[UILabel alloc] initWithFrame:self.view.bounds];
[label setText:@"Hello World from Flax"];
[label setBackgroundColor:[UIColor systemBackgroundColor]];
[label setTextAlignment:NSTextAlignmentCenter];
self.view = label;
}
@end
@interface FlaxAppDelegate()
@property(strong, nonatomic) CADisplayLink* displayLink;
@end
@implementation FlaxAppDelegate
-(void)GameThreadMain:(NSDictionary*)launchOptions
{
// Run engine on a separate game thread
Engine::Main(TEXT(""));
}
-(void)UIThreadMain
{
// Invoke callbacks
UIThreadFuncLocker.Lock();
for (const auto& func : UIThreadFuncList)
{
func();
}
UIThreadFuncList.Clear();
UIThreadFuncLocker.Unlock();
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create window
CGRect frame = [[UIScreen mainScreen] bounds];
self.window = [[UIWindow alloc] initWithFrame:frame];
// Create view controller
self.viewController = [[FlaxViewController alloc] init];
MainViewController = self.viewController;
// Create view
self.view = [[FlaxView alloc] initWithFrame:frame];
[self.view resignFirstResponder];
[self.view setNeedsDisplay];
[self.view setHidden:NO];
[self.view setOpaque:YES];
self.view.backgroundColor = [UIColor clearColor];
MainView = self.view;
// Create navigation controller
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
[self.window setRootViewController:navController];
[self.window makeKeyAndVisible];
// Create UI thread update callback
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(UIThreadMain)];
self.displayLink.preferredFramesPerSecond = 30;
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// Run engine on a separate game thread
NSThread* gameThread = [[NSThread alloc] initWithTarget:self selector:@selector(GameThreadMain:) object:launchOptions];
#if BUILD_DEBUG
const int32 gameThreadStackSize = 4 * 1024 * 1024; // 4 MB
#else
const int32 gameThreadStackSize = 2 * 1024 * 1024; // 2 MB
#endif
[gameThread setStackSize:gameThreadStackSize];
[gameThread start];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
@end
DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
{
NSString* title = (NSString*)AppleUtils::ToString(caption);
@@ -36,28 +176,89 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri
UIAlertController* alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* button = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(id){ }];
[alert addAction:button];
ScopeLock lock(WindowsManager::WindowsLocker);
for (auto window : WindowsManager::Windows)
{
if (window->IsVisible())
{
[(UIViewController*)window->GetViewController() presentViewController:alert animated:YES completion:nil];
break;
}
}
[MainViewController presentViewController:alert animated:YES completion:nil];
return DialogResult::OK;
}
iOSWindow::iOSWindow(const CreateWindowSettings& settings)
: WindowBase(settings)
{
CGRect frame = [[UIScreen mainScreen] bounds];
float scale = [[UIScreen mainScreen] scale];
_clientSize = Float2((float)frame.size.width * scale, (float)frame.size.height * scale);
MainWindow = this;
}
iOSWindow::~iOSWindow()
{
MainWindow = nullptr;
}
void iOSWindow::CheckForResize(float width, float height)
{
const Float2 clientSize(width, height);
if (clientSize != _clientSize)
{
_clientSize = clientSize;
OnResize(width, height);
}
}
void* iOSWindow::GetNativePtr() const
{
return MainView;
}
void iOSWindow::Show()
{
if (!_visible)
{
InitSwapChain();
if (_showAfterFirstPaint)
{
if (RenderTask)
RenderTask->Enabled = true;
return;
}
// Show
_focused = true;
// Base
WindowBase::Show();
}
}
bool iOSWindow::IsClosed() const
{
return false;
}
bool iOSWindow::IsForegroundWindow() const
{
return Platform::GetHasFocus() && IsFocused();
}
void iOSWindow::BringToFront(bool force)
{
Focus();
}
void iOSWindow::SetIsFullscreen(bool isFullscreen)
{
}
// Fallback to file placed side-by-side with application
#define IOS_FALLBACK_PATH(path) (Globals::ProjectFolder / StringUtils::GetFileName(path))
iOSFile* iOSFile::Open(const StringView& path, FileMode mode, FileAccess access, FileShare share)
{
iOSFile* file = (iOSFile*)UnixFile::Open(path, mode, access, share);
if (!file && mode == FileMode::OpenExisting)
{
// Fallback to file placed side-by-side with application
iOSFile* file;
if (mode == FileMode::OpenExisting && !AppleFileSystem::FileExists(path))
file = (iOSFile*)UnixFile::Open(IOS_FALLBACK_PATH(path), mode, access, share);
}
else
file = (iOSFile*)UnixFile::Open(path, mode, access, share);
return file;
}
@@ -82,6 +283,22 @@ bool iOSFileSystem::IsReadOnly(const StringView& path)
#undef IOS_FALLBACK_PATH
void iOSPlatform::RunOnUIThread(const Function<void()>& func, bool wait)
{
UIThreadFuncLocker.Lock();
UIThreadFuncList.Add(func);
UIThreadFuncLocker.Unlock();
// TODO: use atomic counters for more optimized waiting
while (wait)
{
Platform::Sleep(1);
UIThreadFuncLocker.Lock();
wait = UIThreadFuncList.HasItems();
UIThreadFuncLocker.Unlock();
}
}
bool iOSPlatform::Init()
{
if (ApplePlatform::Init())
@@ -113,10 +330,9 @@ void iOSPlatform::LogInfo()
void iOSPlatform::Tick()
{
// Process system events
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0001, true) == kCFRunLoopRunHandledSource)
{
}
// TODO: get events from UI Thread
ApplePlatform::Tick();
}
int32 iOSPlatform::GetDpi()

View File

@@ -11,6 +11,10 @@
/// </summary>
class FLAXENGINE_API iOSPlatform : public ApplePlatform
{
public:
// Runs the callback on a main UI thread (from iOS). Can optionally wait for execution end to sync.
static void RunOnUIThread(const Function<void()>& func, bool wait = false);
public:
// [ApplePlatform]

View File

@@ -13,6 +13,12 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API
{
DECLARE_SCRIPTING_TYPE_MINIMAL(ApplePlatformSettings);
/// <summary>
/// The app developer name - App Store Team ID.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), EditorDisplay(\"General\")")
String AppTeamId;
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
@@ -22,6 +28,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
{
ApplePlatformSettings::Deserialize(stream, modifier);
DESERIALIZE(AppTeamId);
}
};

View File

@@ -1,211 +0,0 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_IOS
#include "../Window.h"
#include "Engine/Platform/Apple/AppleUtils.h"
#include "Engine/Core/Log.h"
#include "Engine/Input/Input.h"
#include "Engine/Graphics/RenderTask.h"
#include <UIKit/UIKit.h>
#include <QuartzCore/CAMetalLayer.h>
@interface iOSUIWindow : UIWindow
{
}
@end
@implementation iOSUIWindow
@end
@interface iOSUIViewController : UIViewController
@end
@interface iOSUIView : UIView
@property iOSWindow* window;
@end
@implementation iOSUIView
+(Class) layerClass { return [CAMetalLayer class]; }
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (!_window)
return;
float scale = [[UIScreen mainScreen] scale];
_window->CheckForResize((float)frame.size.width * scale, (float)frame.size.height * scale);
}
@end
@implementation iOSUIViewController
{
}
- (BOOL)prefersHomeIndicatorAutoHidden
{
return YES;
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return UIStatusBarAnimationSlide;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
@end
iOSWindow::iOSWindow(const CreateWindowSettings& settings)
: WindowBase(settings)
{
// Fullscreen by default
CGRect frame = [[UIScreen mainScreen] bounds];
float scale = [[UIScreen mainScreen] scale];
_clientSize = Float2((float)frame.size.width * scale, (float)frame.size.height * scale);
// Setup view
_view = [[iOSUIView alloc] initWithFrame:frame];
iOSUIView* v = (iOSUIView*)_view;
[v resignFirstResponder];
[v setNeedsDisplay];
[v setHidden:NO];
[v setOpaque:YES];
v.backgroundColor = [UIColor clearColor];
v.window = this;
// Setp view controller
_viewController = [[iOSUIViewController alloc] init];
iOSUIViewController* vc = (iOSUIViewController*)_viewController;
[vc setView:v];
[vc setNeedsUpdateOfHomeIndicatorAutoHidden];
[vc setNeedsStatusBarAppearanceUpdate];
// Setup window
_window = [[iOSUIWindow alloc] initWithFrame:frame];
iOSUIWindow* w = (iOSUIWindow*)_window;
[w setRootViewController:vc];
[w setContentMode:UIViewContentModeScaleToFill];
[w makeKeyAndVisible];
[w setBounds:frame];
w.backgroundColor = [UIColor clearColor];
}
iOSWindow::~iOSWindow()
{
[(iOSUIWindow*)_window release];
[(iOSUIView*)_view release];
[(iOSUIViewController*)_viewController release];
_window = nullptr;
_view = nullptr;
_layer = nullptr;
}
void iOSWindow::CheckForResize(float width, float height)
{
const Float2 clientSize(width, height);
if (clientSize != _clientSize)
{
_clientSize = clientSize;
OnResize(width, height);
}
}
void* iOSWindow::GetNativePtr() const
{
return _window;
}
void iOSWindow::Show()
{
if (!_visible)
{
InitSwapChain();
if (_showAfterFirstPaint)
{
if (RenderTask)
RenderTask->Enabled = true;
return;
}
// Show
_focused = true;
// Base
WindowBase::Show();
}
}
bool iOSWindow::IsClosed() const
{
return _window != nullptr;
}
bool iOSWindow::IsForegroundWindow() const
{
return Platform::GetHasFocus() && IsFocused();
}
void iOSWindow::BringToFront(bool force)
{
Focus();
}
void iOSWindow::SetIsFullscreen(bool isFullscreen)
{
}
void iOSWindow::SetClientBounds(const Rectangle& clientArea)
{
}
void iOSWindow::SetPosition(const Float2& position)
{
}
Float2 iOSWindow::GetPosition() const
{
return Float2::Zero;
}
Float2 iOSWindow::GetSize() const
{
return _clientSize;
}
Float2 iOSWindow::GetClientSize() const
{
return _clientSize;
}
Float2 iOSWindow::ScreenToClient(const Float2& screenPos) const
{
return screenPos;
}
Float2 iOSWindow::ClientToScreen(const Float2& clientPos) const
{
return clientPos;
}
void iOSWindow::SetTitle(const StringView& title)
{
_title = title;
}
#endif

View File

@@ -5,46 +5,26 @@
#if PLATFORM_IOS
#include "Engine/Platform/Base/WindowBase.h"
#include "Engine/Platform/Platform.h"
/// <summary>
/// Implementation of the window class for iOS platform.
/// </summary>
class FLAXENGINE_API iOSWindow : public WindowBase
{
private:
Float2 _clientSize;
void* _window;
void* _layer;
void* _view;
void* _viewController;
public:
iOSWindow(const CreateWindowSettings& settings);
~iOSWindow();
void CheckForResize(float width, float height);
void* GetViewController() const { return _viewController; }
public:
// [Window]
// [WindowBase]
void* GetNativePtr() const override;
void Show() override;
bool IsClosed() const override;
bool IsForegroundWindow() const override;
void BringToFront(bool force = false) override;
void SetClientBounds(const Rectangle& clientArea) override;
void SetPosition(const Float2& position) override;
Float2 GetPosition() const override;
Float2 GetSize() const override;
Float2 GetClientSize() const override;
Float2 ScreenToClient(const Float2& screenPos) const override;
Float2 ClientToScreen(const Float2& clientPos) const override;
void SetIsFullscreen(bool isFullscreen) override;
void SetTitle(const StringView& title) override;
};
#endif

View File

@@ -0,0 +1,391 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
2E6ABCC32A1540AF003DBD6D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2E6ABCC22A1540AF003DBD6D /* Assets.xcassets */; };
2E6ABCC62A1540AF003DBD6D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2E6ABCC42A1540AF003DBD6D /* LaunchScreen.storyboard */; };
2E6ABCC82A1540AF003DBD6D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E6ABCC72A1540AF003DBD6D /* main.m */; };
${PBXBuildFile}
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
2EE436B82A220EAA00206A23 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
${PBXCopyFilesBuildPhaseFiles}
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
2E6ABCB02A1540AD003DBD6D /* FlaxGame.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlaxGame.app; sourceTree = BUILT_PRODUCTS_DIR; };
2E6ABCC22A1540AF003DBD6D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2E6ABCC52A1540AF003DBD6D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
2E6ABCC72A1540AF003DBD6D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
${PBXFileReference}
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2E6ABCAD2A1540AD003DBD6D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
${PBXFrameworksBuildPhase}
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2E6ABCA72A1540AD003DBD6D = {
isa = PBXGroup;
children = (
2E6ABCB22A1540AD003DBD6D /* FlaxGame */,
2E6ABCB12A1540AD003DBD6D /* Products */,
2EE436B42A220EA900206A23 /* Frameworks */,
);
sourceTree = "<group>";
};
2E6ABCB12A1540AD003DBD6D /* Products */ = {
isa = PBXGroup;
children = (
2E6ABCB02A1540AD003DBD6D /* FlaxGame.app */,
);
name = Products;
sourceTree = "<group>";
};
2E6ABCB22A1540AD003DBD6D /* FlaxGame */ = {
isa = PBXGroup;
children = (
${PBXFilesGroup}
2E6ABCC22A1540AF003DBD6D /* Assets.xcassets */,
2E6ABCC42A1540AF003DBD6D /* LaunchScreen.storyboard */,
2E6ABCC72A1540AF003DBD6D /* main.m */,
);
path = FlaxGame;
sourceTree = "<group>";
};
2EE436B42A220EA900206A23 /* Frameworks */ = {
isa = PBXGroup;
children = (
${PBXFrameworksGroup}
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
2E6ABCAF2A1540AD003DBD6D /* FlaxGame */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2E6ABCCB2A1540AF003DBD6D /* Build configuration list for PBXNativeTarget "FlaxGame" */;
buildPhases = (
2E6ABCAC2A1540AD003DBD6D /* Sources */,
2E6ABCAD2A1540AD003DBD6D /* Frameworks */,
2E6ABCAE2A1540AD003DBD6D /* Resources */,
2EE436B82A220EAA00206A23 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = FlaxGame;
productName = ${AppName};
productReference = 2E6ABCB02A1540AD003DBD6D /* FlaxGame.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
2E6ABCA82A1540AD003DBD6D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1430;
TargetAttributes = {
2E6ABCAF2A1540AD003DBD6D = {
CreatedOnToolsVersion = 14.3;
};
};
};
buildConfigurationList = 2E6ABCAB2A1540AD003DBD6D /* Build configuration list for PBXProject "FlaxGame" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 2E6ABCA72A1540AD003DBD6D;
productRefGroup = 2E6ABCB12A1540AD003DBD6D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2E6ABCAF2A1540AD003DBD6D /* FlaxGame */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
2E6ABCAE2A1540AD003DBD6D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
${PBXResourcesGroup}
2E6ABCC62A1540AF003DBD6D /* LaunchScreen.storyboard in Resources */,
2E6ABCC32A1540AF003DBD6D /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2E6ABCAC2A1540AD003DBD6D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2E6ABCC82A1540AF003DBD6D /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
2E6ABCC42A1540AF003DBD6D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
2E6ABCC52A1540AF003DBD6D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
2E6ABCC92A1540AF003DBD6D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ${HeaderSearchPaths};
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
2E6ABCCA2A1540AF003DBD6D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ${HeaderSearchPaths};
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
2E6ABCCC2A1540AF003DBD6D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = ${AppVersion};
DEVELOPMENT_TEAM = ${AppTeamId};
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "${ProjectName}";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = metal;
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UIStatusBarHidden = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/FlaxGame",
"$(PROJECT_DIR)/FlaxGame/Data",
"$(PROJECT_DIR)/FlaxGame/Data/Dotnet",
);
MARKETING_VERSION = ${ProjectVersion};
PRODUCT_BUNDLE_IDENTIFIER = ${AppIdentifier};
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
2E6ABCCD2A1540AF003DBD6D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = ${AppVersion};
DEVELOPMENT_TEAM = ${AppTeamId};
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "${ProjectName}";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = metal;
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UIStatusBarHidden = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/FlaxGame",
"$(PROJECT_DIR)/FlaxGame/Data",
"$(PROJECT_DIR)/FlaxGame/Data/Dotnet",
);
MARKETING_VERSION = ${ProjectVersion};
PRODUCT_BUNDLE_IDENTIFIER = ${AppIdentifier};
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
2E6ABCAB2A1540AD003DBD6D /* Build configuration list for PBXProject "FlaxGame" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2E6ABCC92A1540AF003DBD6D /* Debug */,
2E6ABCCA2A1540AF003DBD6D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2E6ABCCB2A1540AF003DBD6D /* Build configuration list for PBXNativeTarget "FlaxGame" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2E6ABCCC2A1540AF003DBD6D /* Debug */,
2E6ABCCD2A1540AF003DBD6D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 2E6ABCA82A1540AD003DBD6D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>
#import <Source/Engine/Platform/iOS/iOSApp.h>
int main(int argc, char* argv[])
{
NSString* appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([FlaxAppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}