diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index e91d9d6dc..0520f1b63 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -121,6 +121,12 @@ API_ENUM() enum class BuildPlatform /// API_ENUM(Attributes="EditorDisplay(null, \"Mac ARM64\")") MacOSARM64 = 13, + + /// + /// iOS (ARM64) + /// + API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")") + iOSARM64 = 14, }; extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform); diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 84b4333a9..791b205e2 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -63,6 +63,10 @@ #include "Platform/Mac/MacPlatformTools.h" #include "Engine/Platform/Mac/MacPlatformSettings.h" #endif +#if PLATFORM_TOOLS_MAC +#include "Platform/iOS/iOSPlatformTools.h" +#include "Engine/Platform/iOS/iOSPlatformSettings.h" +#endif namespace GameCookerImpl { @@ -141,6 +145,8 @@ const Char* ToString(const BuildPlatform platform) return TEXT("Mac x64"); case BuildPlatform::MacOSARM64: return TEXT("Mac ARM64"); + case BuildPlatform::iOSARM64: + return TEXT("iOS ARM64"); default: return TEXT("?"); } @@ -345,6 +351,11 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform) case BuildPlatform::MacOSARM64: result = New(ArchitectureType::ARM64); break; +#endif +#if PLATFORM_TOOLS_IOS + case BuildPlatform::iOSARM64: + result = New(); + break; #endif } Tools.Add(platform, result); @@ -475,7 +486,10 @@ void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& build buildPlatform = BuildPlatform::PS5; break; case PlatformType::Mac: - buildPlatform = PLATFORM_ARCH_ARM ? BuildPlatform::AndroidARM64 : BuildPlatform::MacOSx64; + buildPlatform = PLATFORM_ARCH_ARM ? BuildPlatform::MacOSARM64 : BuildPlatform::MacOSx64; + break; + case PlatformType::iOS: + buildPlatform = BuildPlatform::iOSARM64; break; default: ; } diff --git a/Source/Editor/Cooker/GameCooker.cs b/Source/Editor/Cooker/GameCooker.cs index aa7a51f24..9a7500d1d 100644 --- a/Source/Editor/Cooker/GameCooker.cs +++ b/Source/Editor/Cooker/GameCooker.cs @@ -105,6 +105,7 @@ namespace FlaxEditor case BuildPlatform.Switch: return PlatformType.Switch; case BuildPlatform.MacOSARM64: case BuildPlatform.MacOSx64: return PlatformType.Mac; + case BuildPlatform.iOSARM64: return PlatformType.iOS; default: throw new ArgumentOutOfRangeException(nameof(buildPlatform), buildPlatform, null); } } diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp new file mode 100644 index 000000000..c5759a5a1 --- /dev/null +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp @@ -0,0 +1,237 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if PLATFORM_TOOLS_IOS + +#include "iOSPlatformTools.h" +#include "Engine/Platform/File.h" +#include "Engine/Platform/FileSystem.h" +#include "Engine/Platform/iOS/iOSPlatformSettings.h" +#include "Engine/Core/Config/GameSettings.h" +#include "Engine/Core/Config/BuildSettings.h" +#include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Content/Content.h" +#include "Engine/Content/JsonAsset.h" +#include "Engine/Engine/Globals.h" +#include "Editor/Editor.h" +#include "Editor/ProjectInfo.h" +#include "Editor/Cooker/GameCooker.h" +#include "Editor/Utilities/EditorUtilities.h" +#include +using namespace pugi; + +IMPLEMENT_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform); + +namespace +{ + String GetAppName() + { + const auto gameSettings = GameSettings::Get(); + String productName = gameSettings->ProductName; + productName.Replace(TEXT(" "), TEXT("")); + productName.Replace(TEXT("."), TEXT("")); + productName.Replace(TEXT("-"), TEXT("")); + return productName; + } +} + +const Char* iOSPlatformTools::GetDisplayName() const +{ + return TEXT("iOS"); +} + +const Char* iOSPlatformTools::GetName() const +{ + return TEXT("iOS"); +} + +PlatformType iOSPlatformTools::GetPlatform() const +{ + return PlatformType::iOS; +} + +ArchitectureType iOSPlatformTools::GetArchitecture() const +{ + return ArchitectureType::ARM64; +} + +bool iOSPlatformTools::IsNativeCodeFile(CookingData& data, const String& file) +{ + String extension = FileSystem::GetExtension(file); + return extension.IsEmpty() || extension == TEXT("dylib"); +} + +void iOSPlatformTools::OnBuildStarted(CookingData& data) +{ + // Adjust the cooking output folders for packaging app + const auto appName = GetAppName(); + String contents = appName + TEXT(".app/Contents/"); + data.DataOutputPath /= contents; + data.NativeCodeOutputPath /= contents / TEXT("iPhoneOS"); + data.ManagedCodeOutputPath /= contents; + + PlatformTools::OnBuildStarted(data); +} + +bool iOSPlatformTools::OnPostProcess(CookingData& data) +{ + const auto gameSettings = GameSettings::Get(); + const auto platformSettings = iOSPlatformSettings::Get(); + const auto platformDataPath = data.GetPlatformBinariesRoot(); + const auto projectVersion = Editor::Project->Version.ToString(); + const auto appName = GetAppName(); + + // Setup package name (eg. com.company.project) + String appIdentifier = platformSettings->AppIdentifier; + { + String productName = gameSettings->ProductName; + productName.Replace(TEXT(" "), TEXT("")); + productName.Replace(TEXT("."), TEXT("")); + productName.Replace(TEXT("-"), TEXT("")); + String companyName = gameSettings->CompanyName; + companyName.Replace(TEXT(" "), TEXT("")); + companyName.Replace(TEXT("."), TEXT("")); + companyName.Replace(TEXT("-"), TEXT("")); + appIdentifier.Replace(TEXT("${PROJECT_NAME}"), *productName, StringSearchCase::IgnoreCase); + appIdentifier.Replace(TEXT("${COMPANY_NAME}"), *companyName, StringSearchCase::IgnoreCase); + appIdentifier = appIdentifier.ToLower(); + for (int32 i = 0; i < appIdentifier.Length(); i++) + { + const auto c = appIdentifier[i]; + if (c != '_' && c != '.' && !StringUtils::IsAlnum(c)) + { + LOG(Error, "Apple app identifier \'{0}\' contains invalid character. Only letters, numbers, dots and underscore characters are allowed.", appIdentifier); + return true; + } + } + if (appIdentifier.IsEmpty()) + { + LOG(Error, "Apple app identifier is empty.", appIdentifier); + return true; + } + } + + // Find executable + String executableName; + { + Array 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 iconFolderPath = data.DataOutputPath / TEXT("Resources"); + String tmpFolderPath = iconFolderPath / 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"), iconFolderPath); + if (failed) + { + LOG(Error, "Failed to export application icon."); + return true; + } + FileSystem::DeleteDirectory(tmpFolderPath); + } + + // 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")); + +#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("NSPrincipalClass", "NSApplication"); + ADD_ENTRY("LSApplicationCategoryType", "public.app-category.games"); + ADD_ENTRY("LSMinimumSystemVersion", "14"); + ADD_ENTRY("CFBundleIconFile", "icon.icns"); + ADD_ENTRY_STR("CFBundleExecutable", executableName); + ADD_ENTRY_STR("CFBundleIdentifier", appIdentifier); + ADD_ENTRY_STR("CFBundleGetInfoString", gameSettings->ProductName); + ADD_ENTRY_STR("CFBundleVersion", projectVersion); + ADD_ENTRY_STR("NSHumanReadableCopyright", gameSettings->CopyrightNotice); + ADD_ENTRY("UIRequiresFullScreen", "true"); + ADD_ENTRY("UIStatusBarHidden", "true"); + + 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")); + + dict.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("LSMinimumSystemVersionByArchitecture")); + xml_node LSMinimumSystemVersionByArchitecture = dict.append_child(PUGIXML_TEXT("dict")); + LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("key")).set_child_value(PUGIXML_TEXT("arm64")); + LSMinimumSystemVersionByArchitecture.append_child(PUGIXML_TEXT("string")).set_child_value(PUGIXML_TEXT("10.15")); + +#undef ADD_ENTRY + + if (!doc.save_file(*StringAnsi(plistPath))) + { + LOG(Error, "Failed to save {0}", plistPath); + return true; + } + } + + // TODO: sign binaries + + // TODO: expose event to inject custom post-processing before app packaging (eg. third-party plugins) + + // Package application + const auto buildSettings = BuildSettings::Get(); + if (buildSettings->SkipPackaging) + return false; + GameCooker::PackageFiles(); + LOG(Info, "Building app package..."); + const String dmgPath = data.OriginalOutputPath / appName + TEXT(".dmg"); + const String dmgCommand = String::Format(TEXT("hdiutil create {0}.dmg -volname {0} -fs HFS+ -srcfolder {0}.app"), appName); + const int32 result = Platform::RunProcess(dmgCommand, data.OriginalOutputPath); + if (result != 0) + { + data.Error(TEXT("Failed to package app (result code: {0}). See log for more info."), result); + return true; + } + // TODO: sign dmg + LOG(Info, "Output application package: {0} (size: {1} MB)", dmgPath, FileSystem::GetFileSize(dmgPath) / 1024 / 1024); + + return false; +} + +#endif diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h new file mode 100644 index 000000000..acc650fd6 --- /dev/null +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_TOOLS_IOS + +#include "../../PlatformTools.h" + +/// +/// The iOS platform support tools. +/// +class iOSPlatformTools : public PlatformTools +{ +public: + // [PlatformTools] + const Char* GetDisplayName() const override; + const Char* GetName() const override; + PlatformType GetPlatform() const override; + ArchitectureType GetArchitecture() const override; + bool IsNativeCodeFile(CookingData& data, const String& file) override; + void OnBuildStarted(CookingData& data) override; + bool OnPostProcess(CookingData& data) override; +}; + +#endif diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp index 10b7eebad..baa628486 100644 --- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp +++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp @@ -208,6 +208,10 @@ bool CompileScriptsStep::Perform(CookingData& data) platform = TEXT("Mac"); architecture = TEXT("ARM64"); break; + case BuildPlatform::iOSARM64: + platform = TEXT("iOS"); + architecture = TEXT("ARM64"); + break; default: LOG(Error, "Unknown or unsupported build platform."); return true; diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index f26002c8e..1f006b6ae 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -534,6 +534,14 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); break; } +#endif +#if PLATFORM_TOOLS_IOS + case BuildPlatform::iOSARM64: + { + const char* platformDefineName = "PLATFORM_IOS"; + COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE); + break; + } #endif default: { diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs index 200903d88..b6ef62d98 100644 --- a/Source/Editor/Editor.Build.cs +++ b/Source/Editor/Editor.Build.cs @@ -73,6 +73,7 @@ public class Editor : EditorModule { AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Mac", "PLATFORM_TOOLS_MAC"); AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID"); + AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "iOS", "PLATFORM_TOOLS_IOS"); } // Visual Studio integration diff --git a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp index 1ea5bb59f..f99c86eee 100644 --- a/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp +++ b/Source/Editor/Scripting/CodeEditors/VisualStudioCodeEditor.cpp @@ -11,7 +11,7 @@ #if PLATFORM_LINUX #include #elif PLATFORM_MAC -#include "Engine/Platform/Mac/MacUtils.h" +#include "Engine/Platform/Apple/AppleUtils.h" #include #endif @@ -88,7 +88,7 @@ void VisualStudioCodeEditor::FindEditors(Array* output) NSURL* AppURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.microsoft.VSCode"]; if (AppURL != nullptr) { - const String path = MacUtils::ToString((CFStringRef)[AppURL path]); + const String path = AppleUtils::ToString((CFStringRef)[AppURL path]); output->Add(New(path, false)); return; } diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs index 082cbc88f..13cefd8a4 100644 --- a/Source/Editor/Surface/Archetypes/Tools.cs +++ b/Source/Editor/Surface/Archetypes/Tools.cs @@ -1523,9 +1523,9 @@ namespace FlaxEditor.Surface.Archetypes Title = "Platform Switch", Description = "Gets the input value based on the runtime-platform type", Flags = NodeFlags.AllGraphs, - Size = new Float2(220, 220), + Size = new Float2(220, 240), ConnectionsHints = ConnectionsHint.Value, - IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, + IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }, DependentBoxes = new[] { 0 }, Elements = new[] { @@ -1541,6 +1541,7 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Input(8, "Switch", true, null, 9), NodeElementArchetype.Factory.Input(9, "PlayStation 5", true, null, 10), NodeElementArchetype.Factory.Input(10, "Mac", true, null, 11), + NodeElementArchetype.Factory.Input(11, "iOS", true, null, 12), } }, new NodeArchetype diff --git a/Source/Engine/Audio/Audio.Build.cs b/Source/Engine/Audio/Audio.Build.cs index 03e7ba2aa..1b0b04514 100644 --- a/Source/Engine/Audio/Audio.Build.cs +++ b/Source/Engine/Audio/Audio.Build.cs @@ -55,6 +55,7 @@ public class Audio : EngineModule options.CompileEnv.PreprocessorDefinitions.Add("AUDIO_API_PS5"); break; case TargetPlatform.Mac: + case TargetPlatform.iOS: useOpenAL = true; break; default: throw new InvalidPlatformException(options.Platform.Target); @@ -86,6 +87,7 @@ public class Audio : EngineModule options.Libraries.Add("OpenSLES"); break; case TargetPlatform.Mac: + case TargetPlatform.iOS: options.OutputFiles.Add(Path.Combine(depsRoot, "libopenal.a")); options.Libraries.Add("CoreAudio.framework"); options.Libraries.Add("AudioUnit.framework"); diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index c64fc0d4e..3784c46ff 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -71,6 +71,8 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); IMPLEMENT_ENGINE_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform); #elif PLATFORM_MAC IMPLEMENT_ENGINE_SETTINGS_GETTER(MacPlatformSettings, MacPlatform); +#elif PLATFORM_IOS +IMPLEMENT_ENGINE_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform); #else #error Unknown platform #endif @@ -254,6 +256,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(SwitchPlatform); DESERIALIZE(PS5Platform); DESERIALIZE(MacPlatform); + DESERIALIZE(iOSPlatform); } void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index af3196e8d..40d9f5dbc 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -202,6 +202,14 @@ namespace FlaxEditor.Content.Settings public JsonAsset MacPlatform; #endif +#if FLAX_EDITOR || PLATFORM_IOS + /// + /// Reference to asset. Used to apply configuration on iOS platform. + /// + [EditorOrder(2100), EditorDisplay("Platform Settings", "iOS"), AssetReference(typeof(iOSPlatformSettings), true), Tooltip("Reference to iOS Platform Settings asset")] + public JsonAsset iOSPlatform; +#endif + /// /// Gets the absolute path to the game settings asset file. /// @@ -333,6 +341,10 @@ namespace FlaxEditor.Content.Settings if (type == typeof(MacPlatformSettings)) return Load(gameSettings.MacPlatform) as T; #endif +#if FLAX_EDITOR || PLATFORM_IOS + if (type == typeof(iOSPlatformSettings)) + return Load(gameSettings.iOSPlatform) as T; +#endif if (gameSettings.CustomSettings != null) { @@ -427,6 +439,10 @@ namespace FlaxEditor.Content.Settings if (type == typeof(MacPlatformSettings)) return gameSettings.MacPlatform; #endif +#if FLAX_EDITOR || PLATFORM_IOS + if (type == typeof(iOSPlatformSettings)) + return gameSettings.iOSPlatform; +#endif if (gameSettings.CustomSettings != null) { @@ -539,6 +555,8 @@ namespace FlaxEditor.Content.Settings return SaveAsset(gameSettings, ref gameSettings.Audio, obj); if (type == typeof(MacPlatformSettings)) return SaveAsset(gameSettings, ref gameSettings.MacPlatform, obj); + if (type == typeof(iOSPlatformSettings)) + return SaveAsset(gameSettings, ref gameSettings.iOSPlatform, obj); return true; } diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index d34dca4de..54ad29a7b 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -84,6 +84,7 @@ public: Guid SwitchPlatform; Guid PS5Platform; Guid MacPlatform; + Guid iOSPlatform; public: diff --git a/Source/Engine/Core/Config/PlatformSettings.h b/Source/Engine/Core/Config/PlatformSettings.h index ed26af514..5353ac7fb 100644 --- a/Source/Engine/Core/Config/PlatformSettings.h +++ b/Source/Engine/Core/Config/PlatformSettings.h @@ -35,3 +35,6 @@ #if PLATFORM_MAC #include "Engine/Platform/Mac/MacPlatformSettings.h" #endif +#if PLATFORM_IOS +#include "Engine/Platform/iOS/iOSPlatformSettings.h" +#endif diff --git a/Source/Engine/Engine/Game.h b/Source/Engine/Engine/Game.h index 16a992c82..6b71b4e1c 100644 --- a/Source/Engine/Engine/Game.h +++ b/Source/Engine/Engine/Game.h @@ -24,6 +24,8 @@ #include "Platforms/Switch/Engine/Engine/SwitchGame.h" #elif PLATFORM_MAC #include "Mac/MacGame.h" +#elif PLATFORM_IOS +#include "iOS/iOSGame.h" #else #error Missing Game implementation! #endif diff --git a/Source/Engine/Engine/iOS/iOSGame.h b/Source/Engine/Engine/iOS/iOSGame.h new file mode 100644 index 000000000..0250a8922 --- /dev/null +++ b/Source/Engine/Engine/iOS/iOSGame.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_IOS + +#include "../Base/GameBase.h" + +/// +/// The game class implementation for iOS platform. +/// +/// +class iOSGame : public GameBase +{ +}; + +typedef iOSGame Game; + +#endif diff --git a/Source/Engine/Graphics/Graphics.Build.cs b/Source/Engine/Graphics/Graphics.Build.cs index 5a617261a..1e17af3f5 100644 --- a/Source/Engine/Graphics/Graphics.Build.cs +++ b/Source/Engine/Graphics/Graphics.Build.cs @@ -75,6 +75,7 @@ public class Graphics : EngineModule options.PrivateDependencies.Add("GraphicsDevicePS5"); break; case TargetPlatform.Android: + case TargetPlatform.iOS: options.PrivateDependencies.Add("GraphicsDeviceVulkan"); break; case TargetPlatform.Switch: diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp index 1e28cc32f..9cd33aaf6 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp @@ -271,7 +271,7 @@ bool GPUTextureVulkan::OnInit() imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; if (useUAV) imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; -#if PLATFORM_MAC +#if PLATFORM_MAC || PLATFORM_IOS // MoltenVK: VK_ERROR_FEATURE_NOT_PRESENT: vkCreateImageView(): 2D views on 3D images can only be used as color attachments. if (IsVolume() && _desc.HasPerSliceViews()) imageInfo.usage &= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; diff --git a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h index bffad847a..1926d65ab 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h +++ b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatform.h @@ -12,4 +12,6 @@ #include "Platforms/Switch/Engine/GraphicsDevice/Vulkan/SwitchVulkanPlatform.h" #elif PLATFORM_MAC #include "Mac/MacVulkanPlatform.h" +#elif PLATFORM_IOS +#include "iOS/iOSVulkanPlatform.h" #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp new file mode 100644 index 000000000..7b0295814 --- /dev/null +++ b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if GRAPHICS_API_VULKAN && PLATFORM_IOS + +#include "iOSVulkanPlatform.h" +#include "../RenderToolsVulkan.h" +#include + +void iOSVulkanPlatform::GetInstanceExtensions(Array& extensions, Array& layers) +{ + extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.Add(VK_MVK_IOS_SURFACE_EXTENSION_NAME); +} + +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)); +} + +#endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h new file mode 100644 index 000000000..dd0b999b8 --- /dev/null +++ b/Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h @@ -0,0 +1,26 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../VulkanPlatformBase.h" + +#if GRAPHICS_API_VULKAN && PLATFORM_IOS + +#define VULKAN_BACK_BUFFERS_COUNT 3 + +// General/Validation Error:0 VK_ERROR_INITIALIZATION_FAILED: Could not create MTLCounterSampleBuffer for query pool of type VK_QUERY_TYPE_TIMESTAMP. Reverting to emulated behavior. (Error code 0): Cannot allocate sample buffer +#define VULKAN_USE_QUERIES 0 + +/// +/// The implementation for the Vulkan API support for iOS platform. +/// +class iOSVulkanPlatform : public VulkanPlatformBase +{ +public: + static void GetInstanceExtensions(Array& extensions, Array& layers); + static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface); +}; + +typedef iOSVulkanPlatform VulkanPlatform; + +#endif diff --git a/Source/Engine/Level/Types.h b/Source/Engine/Level/Types.h index 9a29fde6e..a2f1a6806 100644 --- a/Source/Engine/Level/Types.h +++ b/Source/Engine/Level/Types.h @@ -54,8 +54,6 @@ class AudioListener; class AnimatedModel; class BoneSocket; class Decal; -class UICanvas; -class UIControl; // Default extension for JSON scene files #define DEFAULT_SCENE_EXTENSION TEXT("scene") diff --git a/Source/Engine/Main/Linux/main.cpp b/Source/Engine/Main/Default/main.cpp similarity index 89% rename from Source/Engine/Main/Linux/main.cpp rename to Source/Engine/Main/Default/main.cpp index 144ca4d58..3a7efcc4e 100644 --- a/Source/Engine/Main/Linux/main.cpp +++ b/Source/Engine/Main/Default/main.cpp @@ -1,6 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -#if PLATFORM_LINUX +#if PLATFORM_LINUX || PLATFORM_MAC || PLATFORM_IOS #include "Engine/Engine/Engine.h" #include "Engine/Core/Types/StringBuilder.h" diff --git a/Source/Engine/Main/Mac/main.cpp b/Source/Engine/Main/Mac/main.cpp deleted file mode 100644 index 266b2ca4e..000000000 --- a/Source/Engine/Main/Mac/main.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. - -#if PLATFORM_MAC - -#include "Engine/Engine/Engine.h" -#include "Engine/Core/Types/StringBuilder.h" - -int main(int argc, char* argv[]) -{ - // Join the arguments - StringBuilder args; - for (int i = 1; i < argc; i++) - { - args.Append(argv[i]); - - if (i + 1 != argc) - args.Append(TEXT(' ')); - } - args.Append(TEXT('\0')); - - return Engine::Main(*args); -} - -#endif diff --git a/Source/Engine/Main/Main.Build.cs b/Source/Engine/Main/Main.Build.cs index b061aa303..27c9fb611 100644 --- a/Source/Engine/Main/Main.Build.cs +++ b/Source/Engine/Main/Main.Build.cs @@ -50,9 +50,6 @@ public class Main : EngineModule options.CompileEnv.WinRTComponentExtensions = true; options.CompileEnv.GenerateDocumentation = true; - break; - case TargetPlatform.Linux: - options.SourcePaths.Add(Path.Combine(FolderPath, "Linux")); break; case TargetPlatform.PS4: options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Main")); @@ -72,8 +69,10 @@ public class Main : EngineModule case TargetPlatform.Switch: options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "Main")); break; + case TargetPlatform.Linux: case TargetPlatform.Mac: - options.SourcePaths.Add(Path.Combine(FolderPath, "Mac")); + case TargetPlatform.iOS: + options.SourcePaths.Add(Path.Combine(FolderPath, "Default")); break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/Engine/Platform/Android/AndroidPlatform.cpp b/Source/Engine/Platform/Android/AndroidPlatform.cpp index 28481c97f..c61e2aadd 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.cpp +++ b/Source/Engine/Platform/Android/AndroidPlatform.cpp @@ -996,16 +996,6 @@ Float2 AndroidPlatform::GetDesktopSize() return Float2((float)ScreenWidth, (float)ScreenHeight); } -Rectangle AndroidPlatform::GetMonitorBounds(const Float2& screenPos) -{ - return Rectangle(Float2::Zero, GetDesktopSize()); -} - -Rectangle AndroidPlatform::GetVirtualDesktopBounds() -{ - return Rectangle(Float2::Zero, GetDesktopSize()); -} - String AndroidPlatform::GetMainDirectory() { return String(App->activity->internalDataPath); diff --git a/Source/Engine/Platform/Android/AndroidPlatform.h b/Source/Engine/Platform/Android/AndroidPlatform.h index 941836ce6..07e72cac4 100644 --- a/Source/Engine/Platform/Android/AndroidPlatform.h +++ b/Source/Engine/Platform/Android/AndroidPlatform.h @@ -120,9 +120,7 @@ public: static void OpenUrl(const StringView& url); static Float2 GetMousePosition(); static void SetMousePosition(const Float2& pos); - static Rectangle GetMonitorBounds(const Float2& screenPos); static Float2 GetDesktopSize(); - static Rectangle GetVirtualDesktopBounds(); static String GetMainDirectory(); static String GetExecutableFilePath(); static Guid GetUniqueDeviceId(); diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.cpp b/Source/Engine/Platform/Apple/AppleFileSystem.cpp new file mode 100644 index 000000000..e1b19ef56 --- /dev/null +++ b/Source/Engine/Platform/Apple/AppleFileSystem.cpp @@ -0,0 +1,532 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if PLATFORM_MAC || PLATFORM_IOS + +#include "AppleFileSystem.h" +#include "AppleUtils.h" +#include "Engine/Platform/File.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Core/Log.h" +#include "Engine/Utilities/StringConverter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const DateTime UnixEpoch(1970, 1, 1); + +bool AppleFileSystem::CreateDirectory(const StringView& path) +{ + const StringAsANSI<> pathAnsi(*path, path.Length()); + + // Skip if already exists + struct stat fileInfo; + if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode)) + { + return false; + } + + // Recursively do it all again for the parent directory, if any + const int32 slashIndex = path.FindLast('/'); + if (slashIndex > 1) + { + if (CreateDirectory(path.Substring(0, slashIndex))) + { + return true; + } + } + + // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now) + return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST; +} + +bool DeletePathTree(const char* path) +{ + size_t pathLength; + DIR* dir; + struct stat statPath, statEntry; + struct dirent* entry; + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + if ((dir = opendir(path)) == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char full_path[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); + strcpy(full_path, path); + strcat(full_path, "/"); + strcat(full_path, entry->d_name); + + // Stat for the entry + stat(full_path, &statEntry); + + // Recursively remove a nested directory + if (S_ISDIR(statEntry.st_mode) != 0) + { + if (DeletePathTree(full_path)) + return true; + continue; + } + + // Remove a file object + if (unlink(full_path) != 0) + return true; + } + + // Remove the devastated directory and close the object of it + if (rmdir(path) != 0) + return true; + + closedir(dir); + + return false; +} + +bool AppleFileSystem::DeleteDirectory(const String& path, bool deleteContents) +{ + const StringAsANSI<> pathANSI(*path, path.Length()); + if (deleteContents) + return DeletePathTree(pathANSI.Get()); + return rmdir(pathANSI.Get()) != 0; +} + +bool AppleFileSystem::DirectoryExists(const StringView& path) +{ + struct stat fileInfo; + const StringAsANSI<> pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + return S_ISDIR(fileInfo.st_mode); + } + return false; +} + +bool AppleFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option) +{ + const StringAsANSI<> pathANSI(*path, path.Length()); + const StringAsANSI<> searchPatternANSI(searchPattern); + + // Check if use only top directory + if (option == DirectorySearchOption::TopDirectoryOnly) + return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get()); + return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get()); +} + +bool AppleFileSystem::GetChildDirectories(Array& results, const String& directory) +{ + size_t pathLength; + DIR* dir; + struct stat statPath, statEntry; + struct dirent* entry; + const StringAsANSI<> pathANSI(*directory, directory.Length()); + const char* path = pathANSI.Get(); + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + if ((dir = opendir(path)) == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char full_path[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); + strcpy(full_path, path); + strcat(full_path, "/"); + strcat(full_path, entry->d_name); + + // Stat for the entry + stat(full_path, &statEntry); + + // Check for directory + if (S_ISDIR(statEntry.st_mode) != 0) + { + // Add directory + results.Add(String(full_path)); + } + } + + closedir(dir); + + return false; +} + +bool AppleFileSystem::FileExists(const StringView& path) +{ + struct stat fileInfo; + const StringAsANSI<> pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + return S_ISREG(fileInfo.st_mode); + } + return false; +} + +bool AppleFileSystem::DeleteFile(const StringView& path) +{ + const StringAsANSI<> pathANSI(*path, path.Length()); + return unlink(pathANSI.Get()) == 0; +} + +uint64 AppleFileSystem::GetFileSize(const StringView& path) +{ + struct stat fileInfo; + fileInfo.st_size = -1; + const StringAsANSI<> pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + // Check for directories + if (S_ISDIR(fileInfo.st_mode)) + { + fileInfo.st_size = -1; + } + } + return fileInfo.st_size; +} + +bool AppleFileSystem::IsReadOnly(const StringView& path) +{ + const StringAsANSI<> pathANSI(*path, path.Length()); + if (access(pathANSI.Get(), W_OK) == -1) + { + return errno == EACCES; + } + return false; +} + +bool AppleFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) +{ + const StringAsANSI<> pathANSI(*path, path.Length()); + struct stat fileInfo; + if (stat(pathANSI.Get(), &fileInfo) != -1) + { + if (isReadOnly) + { + fileInfo.st_mode &= ~S_IWUSR; + } + else + { + fileInfo.st_mode |= S_IWUSR; + } + return chmod(pathANSI.Get(), fileInfo.st_mode) == 0; + } + return false; +} + +bool AppleFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite) +{ + if (!overwrite && FileExists(dst)) + { + // Already exists + return true; + } + + if (overwrite) + { + unlink(StringAsANSI<>(*dst, dst.Length()).Get()); + } + if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0) + { + if (errno == EXDEV) + { + if (!CopyFile(dst, src)) + { + unlink(StringAsANSI<>(*src, src.Length()).Get()); + return false; + } + } + return true; + } + return false; +} + +bool AppleFileSystem::CopyFile(const StringView& dst, const StringView& src) +{ + const StringAsANSI<> srcANSI(*src, src.Length()); + const StringAsANSI<> dstANSI(*dst, dst.Length()); + + int srcFile, dstFile; + char buffer[4096]; + ssize_t readSize; + int cachedError; + + srcFile = open(srcANSI.Get(), O_RDONLY); + if (srcFile < 0) + return true; + dstFile = open(dstANSI.Get(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (dstFile < 0) + goto out_error; + + while (readSize = read(srcFile, buffer, sizeof(buffer)), readSize > 0) + { + char* ptr = buffer; + ssize_t writeSize; + + do + { + writeSize = write(dstFile, ptr, readSize); + if (writeSize >= 0) + { + readSize -= writeSize; + ptr += writeSize; + } + else if (errno != EINTR) + { + goto out_error; + } + } while (readSize > 0); + } + + if (readSize == 0) + { + if (close(dstFile) < 0) + { + dstFile = -1; + goto out_error; + } + close(srcFile); + + // Success + return false; + } + +out_error: + cachedError = errno; + close(srcFile); + if (dstFile >= 0) + close(dstFile); + errno = cachedError; + return true; +} + +bool AppleFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) +{ + size_t pathLength; + struct stat statPath, statEntry; + struct dirent* entry; + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + DIR* dir = opendir(path); + if (dir == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char fullPath[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); + strcpy(fullPath, path); + strcat(fullPath, "/"); + strcat(fullPath, entry->d_name); + + // Stat for the entry + stat(fullPath, &statEntry); + + // Check for file + if (S_ISREG(statEntry.st_mode) != 0) + { + // Validate with filter + const int32 fullPathLength = StringUtils::Length(fullPath); + const int32 searchPatternLength = StringUtils::Length(searchPattern); + if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0) + { + // All files + } + else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0) + { + // Path ending + } + else + { + // TODO: implement all cases in a generic way + continue; + } + + // Add file + results.Add(String(fullPath)); + } + } + + closedir(dir); + + return false; +} + +bool AppleFileSystem::getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern) +{ + // Find all files in this directory + getFilesFromDirectoryTop(results, path, searchPattern); + + size_t pathLength; + DIR* dir; + struct stat statPath, statEntry; + struct dirent* entry; + + // Stat for the path + stat(path, &statPath); + + // If path does not exists or is not dir - exit with status -1 + if (S_ISDIR(statPath.st_mode) == 0) + { + // Is not directory + return true; + } + + // If not possible to read the directory for this user + if ((dir = opendir(path)) == NULL) + { + // Cannot open directory + return true; + } + + // The length of the path + pathLength = strlen(path); + + // Iteration through entries in the directory + while ((entry = readdir(dir)) != NULL) + { + // Skip entries "." and ".." + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) + continue; + + // Determinate a full path of an entry + char full_path[256]; + ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); + strcpy(full_path, path); + strcat(full_path, "/"); + strcat(full_path, entry->d_name); + + // Stat for the entry + stat(full_path, &statEntry); + + // Check for directory + if (S_ISDIR(statEntry.st_mode) != 0) + { + if (getFilesFromDirectoryAll(results, full_path, searchPattern)) + { + closedir(dir); + return true; + } + } + } + + closedir(dir); + + return false; +} + +DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path) +{ + struct stat fileInfo; + const StringAsANSI<> pathANSI(*path, path.Length()); + if (stat(pathANSI.Get(), &fileInfo) == -1) + { + return DateTime::MinValue(); + } + + const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime); + return UnixEpoch + timeSinceEpoch; +} + +void AppleFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result) +{ + String home; + Platform::GetEnvironmentVariable(TEXT("HOME"), home); + switch (type) + { + case SpecialFolder::Desktop: + result = home / TEXT("/Desktop"); + break; + case SpecialFolder::Documents: + result = home / TEXT("/Documents"); + break; + case SpecialFolder::Pictures: + result = home / TEXT("/Pictures"); + break; + case SpecialFolder::AppData: + case SpecialFolder::LocalAppData: + result = home / TEXT("/Library/Caches"); + break; + case SpecialFolder::ProgramData: + result = home / TEXT("/Library/Application Support"); + break; + case SpecialFolder::Temporary: + Platform::GetEnvironmentVariable(TEXT("TMPDIR"), result); + break; + } +} + +#endif diff --git a/Source/Engine/Platform/Apple/AppleFileSystem.h b/Source/Engine/Platform/Apple/AppleFileSystem.h new file mode 100644 index 000000000..b07f91fb1 --- /dev/null +++ b/Source/Engine/Platform/Apple/AppleFileSystem.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_MAC || PLATFORM_IOS + +#include "Engine/Platform/Base/FileSystemBase.h" + +/// +/// Apple platform implementation of filesystem service. +/// +class FLAXENGINE_API AppleFileSystem : public FileSystemBase +{ +public: + + // [FileSystemBase] + static bool CreateDirectory(const StringView& path); + static bool DeleteDirectory(const String& path, bool deleteContents = true); + static bool DirectoryExists(const StringView& path); + static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option = DirectorySearchOption::AllDirectories); + static bool GetChildDirectories(Array& results, const String& directory); + static bool FileExists(const StringView& path); + static bool DeleteFile(const StringView& path); + static uint64 GetFileSize(const StringView& path); + static bool IsReadOnly(const StringView& path); + static bool SetReadOnly(const StringView& path, bool isReadOnly); + static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false); + static bool CopyFile(const StringView& dst, const StringView& src); + +public: + + /// + /// Gets last time when file has been modified (in UTC). + /// + /// The file path to check. + /// The last write time or DateTime::MinValue() if cannot get data. + static DateTime GetFileLastEditTime(const StringView& path); + + /// + /// Gets the special folder path. + /// + /// The folder type. + /// The result full path. + static void GetSpecialFolderPath(const SpecialFolder type, String& result); + +private: + + static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); + static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); +}; + +#endif diff --git a/Source/Engine/Platform/Apple/ApplePlatform.cpp b/Source/Engine/Platform/Apple/ApplePlatform.cpp new file mode 100644 index 000000000..a7ceaec26 --- /dev/null +++ b/Source/Engine/Platform/Apple/ApplePlatform.cpp @@ -0,0 +1,418 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if PLATFORM_MAC || PLATFORM_IOS + +#include "ApplePlatform.h" +#include "AppleUtils.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Types/Guid.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Core/Math/Rectangle.h" +#include "Engine/Core/Math/Color32.h" +#include "Engine/Platform/CPUInfo.h" +#include "Engine/Platform/MemoryStats.h" +#include "Engine/Platform/StringUtils.h" +#include "Engine/Platform/WindowsManager.h" +#include "Engine/Platform/Clipboard.h" +#include "Engine/Platform/IGuiData.h" +#include "Engine/Platform/Base/PlatformUtils.h" +#include "Engine/Utilities/StringConverter.h" +#include "Engine/Threading/Threading.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Engine/CommandLine.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if CRASH_LOG_ENABLE +#include +#endif + +CPUInfo Cpu; +String UserLocale; +double SecondsPerCycle; + +float ApplePlatform::ScreenScale = 1.0f; + +String AppleUtils::ToString(CFStringRef str) +{ + if (!str) + return String::Empty; + String result; + const int32 length = CFStringGetLength(str); + if (length > 0) + { + CFRange range = CFRangeMake(0, length); + result.ReserveSpace(length); + CFStringGetBytes(str, range, kCFStringEncodingUTF16LE, '?', false, (uint8*)result.Get(), length * sizeof(Char), nullptr); + } + return result; +} + +CFStringRef AppleUtils::ToString(const StringView& str) +{ + return CFStringCreateWithBytes(nullptr, (const UInt8*)str.GetText(), str.Length() * sizeof(Char), kCFStringEncodingUTF16LE, false); +} + +typedef uint16_t offset_t; +#define align_mem_up(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +bool ApplePlatform::Is64BitPlatform() +{ + return PLATFORM_64BITS; +} + +CPUInfo ApplePlatform::GetCPUInfo() +{ + return Cpu; +} + +int32 ApplePlatform::GetCacheLineSize() +{ + return Cpu.CacheLineSize; +} + +MemoryStats ApplePlatform::GetMemoryStats() +{ + MemoryStats result; + int64 value64; + size_t value64Size = sizeof(value64); + if (sysctlbyname("hw.memsize", &value64, &value64Size, nullptr, 0) != 0) + value64 = 1024 * 1024; + result.TotalPhysicalMemory = value64; + int id[] = { CTL_HW, HW_MEMSIZE }; + if (sysctl(id, 2, &value64, &value64Size, nullptr, 0) != 0) + value64Size = 1024; + result.UsedPhysicalMemory = value64Size; + xsw_usage swapusage; + size_t swapusageSize = sizeof(swapusage); + result.TotalVirtualMemory = result.TotalPhysicalMemory; + result.UsedVirtualMemory = result.UsedPhysicalMemory; + if (sysctlbyname("vm.swapusage", &swapusage, &swapusageSize, nullptr, 0) == 0) + { + result.TotalVirtualMemory += swapusage.xsu_total; + result.UsedVirtualMemory += swapusage.xsu_used; + } + return result; +} + +ProcessMemoryStats ApplePlatform::GetProcessMemoryStats() +{ + ProcessMemoryStats result; + result.UsedPhysicalMemory = 1024; + result.UsedVirtualMemory = 1024; + return result; +} + +uint64 ApplePlatform::GetCurrentThreadID() +{ + return (uint64)pthread_mach_thread_np(pthread_self()); +} + +void ApplePlatform::SetThreadPriority(ThreadPriority priority) +{ + // TODO: impl this +} + +void ApplePlatform::SetThreadAffinityMask(uint64 affinityMask) +{ + // TODO: impl this +} + +void ApplePlatform::Sleep(int32 milliseconds) +{ + usleep(milliseconds * 1000); +} + +double ApplePlatform::GetTimeSeconds() +{ + return SecondsPerCycle * mach_absolute_time(); +} + +uint64 ApplePlatform::GetTimeCycles() +{ + return mach_absolute_time(); +} + +uint64 ApplePlatform::GetClockFrequency() +{ + return (uint64)(1.0 / SecondsPerCycle); +} + +void ApplePlatform::GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond) +{ + // Query for calendar time + struct timeval time; + gettimeofday(&time, nullptr); + + // Convert to local time + struct tm localTime; + localtime_r(&time.tv_sec, &localTime); + + // Extract time + year = localTime.tm_year + 1900; + month = localTime.tm_mon + 1; + dayOfWeek = localTime.tm_wday; + day = localTime.tm_mday; + hour = localTime.tm_hour; + minute = localTime.tm_min; + second = localTime.tm_sec; + millisecond = time.tv_usec / 1000; +} + +void ApplePlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond) +{ + // Get the calendar time + struct timeval time; + gettimeofday(&time, nullptr); + + // Convert to UTC time + struct tm localTime; + gmtime_r(&time.tv_sec, &localTime); + + // Extract time + year = localTime.tm_year + 1900; + month = localTime.tm_mon + 1; + dayOfWeek = localTime.tm_wday; + day = localTime.tm_mday; + hour = localTime.tm_hour; + minute = localTime.tm_min; + second = localTime.tm_sec; + millisecond = time.tv_usec / 1000; +} + +bool ApplePlatform::Init() +{ + if (UnixPlatform::Init()) + return true; + + // Init timing + { + mach_timebase_info_data_t info; + mach_timebase_info(&info); + SecondsPerCycle = 1e-9 * (double)info.numer / (double)info.denom; + } + + // Get CPU info + int32 value32; + int64 value64; + size_t value32Size = sizeof(value32), value64Size = sizeof(value64); + if (sysctlbyname("hw.packages", &value32, &value32Size, nullptr, 0) != 0) + value32 = 1; + Cpu.ProcessorPackageCount = value32; + if (sysctlbyname("hw.physicalcpu", &value32, &value32Size, nullptr, 0) != 0) + value32 = 1; + Cpu.ProcessorCoreCount = value32; + if (sysctlbyname("hw.logicalcpu", &value32, &value32Size, nullptr, 0) != 0) + value32 = 1; + Cpu.LogicalProcessorCount = value32; + if (sysctlbyname("hw.l1icachesize", &value32, &value32Size, nullptr, 0) != 0) + value32 = 0; + Cpu.L1CacheSize = value32; + if (sysctlbyname("hw.l2cachesize", &value32, &value32Size, nullptr, 0) != 0) + value32 = 0; + Cpu.L2CacheSize = value32; + if (sysctlbyname("hw.l3cachesize", &value32, &value32Size, nullptr, 0) != 0) + value32 = 0; + Cpu.L3CacheSize = value32; + if (sysctlbyname("hw.pagesize", &value32, &value32Size, nullptr, 0) != 0) + value32 = vm_page_size; + Cpu.PageSize = value32; + if (sysctlbyname("hw.cpufrequency_max", &value64, &value64Size, nullptr, 0) != 0) + value64 = GetClockFrequency(); + Cpu.ClockSpeed = value64; + if (sysctlbyname("hw.cachelinesize", &value32, &value32Size, nullptr, 0) != 0) + value32 = PLATFORM_CACHE_LINE_SIZE; + Cpu.CacheLineSize = value32; + + // Get locale + { + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFStringRef localeLang = (CFStringRef)CFLocaleGetValue(locale, kCFLocaleLanguageCode); + CFStringRef localeCountry = (CFStringRef)CFLocaleGetValue(locale, kCFLocaleCountryCode); + UserLocale = AppleUtils::ToString(localeLang); + String localeCountryStr = AppleUtils::ToString(localeCountry); + if (localeCountryStr.HasChars()) + UserLocale += TEXT("-") + localeCountryStr; + CFRelease(locale); + CFRelease(localeLang); + CFRelease(localeCountry); + } + + // Init user + { + String username; + GetEnvironmentVariable(TEXT("USER"), username); + OnPlatformUserAdd(New(username)); + } + + return false; +} + +void ApplePlatform::Tick() +{ +} + +void ApplePlatform::BeforeExit() +{ +} + +void ApplePlatform::Exit() +{ +} + +void ApplePlatform::SetHighDpiAwarenessEnabled(bool enable) +{ + // Disable resolution scaling in low dpi mode + if (!enable) + { + CustomDpiScale /= ScreenScale; + ScreenScale = 1.0f; + } +} + +String ApplePlatform::GetUserLocaleName() +{ + return UserLocale; +} + +bool ApplePlatform::GetHasFocus() +{ + // Check if any window is focused + ScopeLock lock(WindowsManager::WindowsLocker); + for (auto window : WindowsManager::Windows) + { + if (window->IsFocused()) + return true; + } + + // Default to true if has no windows open + return WindowsManager::Windows.IsEmpty(); +} + +void ApplePlatform::CreateGuid(Guid& result) +{ + uuid_t uuid; + uuid_generate(uuid); + auto ptr = (uint32*)&uuid; + result.A = ptr[0]; + result.B = ptr[1]; + result.C = ptr[2]; + result.D = ptr[3]; +} + +bool ApplePlatform::CanOpenUrl(const StringView& url) +{ + return false; +} + +void ApplePlatform::OpenUrl(const StringView& url) +{ +} + +String ApplePlatform::GetExecutableFilePath() +{ + char buf[PATH_MAX]; + uint32 size = PATH_MAX; + String result; + if (_NSGetExecutablePath(buf, &size) == 0) + result.SetUTF8(buf, StringUtils::Length(buf)); + return result; +} + +String ApplePlatform::GetWorkingDirectory() +{ + char buffer[256]; + getcwd(buffer, ARRAY_COUNT(buffer)); + return String(buffer); +} + +bool ApplePlatform::SetWorkingDirectory(const String& path) +{ + return chdir(StringAsANSI<>(*path).Get()) != 0; +} + +bool ApplePlatform::GetEnvironmentVariable(const String& name, String& value) +{ + char* env = getenv(StringAsANSI<>(*name).Get()); + if (env) + { + value = String(env); + return false; + } + return true; +} + +bool ApplePlatform::SetEnvironmentVariable(const String& name, const String& value) +{ + return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0; +} + +void* ApplePlatform::LoadLibrary(const Char* filename) +{ + const StringAsANSI<> filenameANSI(filename); + void* result = dlopen(filenameANSI.Get(), RTLD_LAZY | RTLD_LOCAL); + if (!result) + { + LOG(Error, "Failed to load {0} because {1}", filename, String(dlerror())); + } + return result; +} + +void ApplePlatform::FreeLibrary(void* handle) +{ + dlclose(handle); +} + +void* ApplePlatform::GetProcAddress(void* handle, const char* symbol) +{ + return dlsym(handle, symbol); +} + +Array ApplePlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context) +{ + Array result; +#if CRASH_LOG_ENABLE + void* callstack[120]; + skipCount = Math::Min(skipCount, ARRAY_COUNT(callstack)); + int32 maxCount = Math::Min(ARRAY_COUNT(callstack), skipCount + maxDepth); + int32 count = backtrace(callstack, maxCount); + int32 useCount = count - skipCount; + if (useCount > 0) + { + char** names = backtrace_symbols(callstack + skipCount, useCount); + result.Resize(useCount); + for (int32 i = 0; i < useCount; i++) + { + char* name = names[i]; + StackFrame& frame = result[i]; + frame.ProgramCounter = callstack[skipCount + i]; + frame.ModuleName[0] = 0; + frame.FileName[0] = 0; + frame.LineNumber = 0; + int32 nameLen = Math::Min(StringUtils::Length(name), ARRAY_COUNT(frame.FunctionName) - 1); + Platform::MemoryCopy(frame.FunctionName, name, nameLen); + frame.FunctionName[nameLen] = 0; + + } + free(names); + } +#endif + return result; +} + +#endif diff --git a/Source/Engine/Platform/Apple/ApplePlatform.h b/Source/Engine/Platform/Apple/ApplePlatform.h new file mode 100644 index 000000000..f072bf222 --- /dev/null +++ b/Source/Engine/Platform/Apple/ApplePlatform.h @@ -0,0 +1,107 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_MAC || PLATFORM_IOS + +#include "../Unix/UnixPlatform.h" + +/// +/// The Apple platform implementation and application management utilities. +/// +class FLAXENGINE_API ApplePlatform : public UnixPlatform +{ +public: + static float ScreenScale; + +public: + + // [UnixPlatform] + FORCE_INLINE static void MemoryBarrier() + { + __sync_synchronize(); + } + FORCE_INLINE static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) + { + return __sync_lock_test_and_set(dst, exchange); + } + FORCE_INLINE static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand) + { + return __sync_val_compare_and_swap(dst, comperand, exchange); + } + FORCE_INLINE static int64 InterlockedCompareExchange(int64 volatile* dst, int64 exchange, int64 comperand) + { + return __sync_val_compare_and_swap(dst, comperand, exchange); + } + FORCE_INLINE static int64 InterlockedIncrement(int64 volatile* dst) + { + return __sync_add_and_fetch(dst, 1); + } + FORCE_INLINE static int64 InterlockedDecrement(int64 volatile* dst) + { + return __sync_sub_and_fetch(dst, 1); + } + FORCE_INLINE static int64 InterlockedAdd(int64 volatile* dst, int64 value) + { + return __sync_fetch_and_add(dst, value); + } + FORCE_INLINE static int32 AtomicRead(int32 volatile* dst) + { + return __atomic_load_n(dst, __ATOMIC_RELAXED); + } + FORCE_INLINE static int64 AtomicRead(int64 volatile* dst) + { + return __atomic_load_n(dst, __ATOMIC_RELAXED); + } + FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value) + { + __atomic_store(dst, &value, __ATOMIC_SEQ_CST); + } + FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value) + { + __atomic_store(dst, &value, __ATOMIC_SEQ_CST); + } + FORCE_INLINE static void Prefetch(void const* ptr) + { + __builtin_prefetch(static_cast(ptr)); + } + static bool Is64BitPlatform(); + static CPUInfo GetCPUInfo(); + static int32 GetCacheLineSize(); + static MemoryStats GetMemoryStats(); + static ProcessMemoryStats GetProcessMemoryStats(); + static uint64 GetCurrentThreadID(); + static void SetThreadPriority(ThreadPriority priority); + static void SetThreadAffinityMask(uint64 affinityMask); + static void Sleep(int32 milliseconds); + static double GetTimeSeconds(); + static uint64 GetTimeCycles(); + static uint64 GetClockFrequency(); + static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); + static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); + static bool Init(); + static void Tick(); + static void BeforeExit(); + static void Exit(); + static void SetHighDpiAwarenessEnabled(bool enable); + static String GetUserLocaleName(); + static bool GetHasFocus(); + static void CreateGuid(Guid& result); + static bool CanOpenUrl(const StringView& url); + static void OpenUrl(const StringView& url); + static Rectangle GetMonitorBounds(const Float2& screenPos); + static Float2 GetDesktopSize(); + static Rectangle GetVirtualDesktopBounds(); + static String GetMainDirectory(); + static String GetExecutableFilePath(); + static String GetWorkingDirectory(); + static bool SetWorkingDirectory(const String& path); + static bool GetEnvironmentVariable(const String& name, String& value); + static bool SetEnvironmentVariable(const String& name, const String& value); + static void* LoadLibrary(const Char* filename); + static void FreeLibrary(void* handle); + static void* GetProcAddress(void* handle, const char* symbol); + static Array GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr); +}; + +#endif diff --git a/Source/Engine/Platform/Mac/MacThread.h b/Source/Engine/Platform/Apple/AppleThread.h similarity index 78% rename from Source/Engine/Platform/Mac/MacThread.h rename to Source/Engine/Platform/Apple/AppleThread.h index f49969628..43d9d2966 100644 --- a/Source/Engine/Platform/Mac/MacThread.h +++ b/Source/Engine/Platform/Apple/AppleThread.h @@ -2,25 +2,25 @@ #pragma once -#if PLATFORM_MAC +#if PLATFORM_MAC || PLATFORM_IOS #include "../Unix/UnixThread.h" #include /// -/// Thread object for Mac platform. +/// Thread object for Apple platform. /// -class FLAXENGINE_API MacThread : public UnixThread +class FLAXENGINE_API AppleThread : public UnixThread { public: /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The runnable. /// The thread name. /// The thread priority. - MacThread(IRunnable* runnable, const String& name, ThreadPriority priority) + AppleThread(IRunnable* runnable, const String& name, ThreadPriority priority) : UnixThread(runnable, name, priority) { } @@ -35,9 +35,9 @@ public: /// Tells the thread whether it needs to adjust its priority or not. Defaults to normal priority /// The size of the stack to create. 0 means use the current thread's stack size /// Pointer to the new thread or null if cannot create it - static MacThread* Create(IRunnable* runnable, const String& name, ThreadPriority priority = ThreadPriority::Normal, uint32 stackSize = 0) + static AppleThread* Create(IRunnable* runnable, const String& name, ThreadPriority priority = ThreadPriority::Normal, uint32 stackSize = 0) { - return (MacThread*)Setup(New(runnable, name, priority), stackSize); + return (AppleThread*)Setup(New(runnable, name, priority), stackSize); } protected: diff --git a/Source/Engine/Platform/Mac/MacUtils.h b/Source/Engine/Platform/Apple/AppleUtils.h similarity index 78% rename from Source/Engine/Platform/Mac/MacUtils.h rename to Source/Engine/Platform/Apple/AppleUtils.h index b89ff24b6..d6f639042 100644 --- a/Source/Engine/Platform/Mac/MacUtils.h +++ b/Source/Engine/Platform/Apple/AppleUtils.h @@ -2,15 +2,22 @@ #pragma once +#if PLATFORM_MAC || PLATFORM_IOS + #include "Engine/Core/Types/String.h" #include -class FLAXENGINE_API MacUtils +// Apple platform utilities. +class AppleUtils { public: static String ToString(CFStringRef str); static CFStringRef ToString(const StringView& str); +#if PLATFORM_MAC static Float2 PosToCoca(const Float2& pos); static Float2 CocaToPos(const Float2& pos); static Float2 GetScreensOrigin(); +#endif }; + +#endif diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 353e2b8f9..99e2a5d06 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -520,6 +520,16 @@ void PlatformBase::CreateGuid(Guid& result) result = Guid(dateThingHigh, randomThing | (sequentialThing << 16), cyclesThing, dateThingLow); } +Rectangle PlatformBase::GetMonitorBounds(const Float2& screenPos) +{ + return Rectangle(Float2::Zero, Platform::GetDesktopSize()); +} + +Rectangle PlatformBase::GetVirtualDesktopBounds() +{ + return Rectangle(Float2::Zero, Platform::GetDesktopSize()); +} + Float2 PlatformBase::GetVirtualDesktopSize() { return Platform::GetVirtualDesktopBounds().Size; @@ -616,6 +626,8 @@ const Char* ToString(PlatformType type) return TEXT("PlayStation 5"); case PlatformType::Mac: return TEXT("Mac"); + case PlatformType::iOS: + return TEXT("iOS"); default: return TEXT(""); } diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h index e7800c94d..4927f17d4 100644 --- a/Source/Engine/Platform/Base/PlatformBase.h +++ b/Source/Engine/Platform/Base/PlatformBase.h @@ -665,7 +665,7 @@ public: /// /// The screen position (in pixels). /// The monitor bounds. - API_FUNCTION() static Rectangle GetMonitorBounds(const Float2& screenPos) = delete; + API_FUNCTION() static Rectangle GetMonitorBounds(const Float2& screenPos); /// /// Gets size of the primary desktop. @@ -677,7 +677,7 @@ public: /// Gets virtual bounds of the desktop made of all the monitors outputs attached. /// /// Whole desktop size. - API_PROPERTY() static Rectangle GetVirtualDesktopBounds() = delete; + API_PROPERTY() static Rectangle GetVirtualDesktopBounds(); /// /// Gets virtual size of the desktop made of all the monitors outputs attached. diff --git a/Source/Engine/Platform/ConditionVariable.h b/Source/Engine/Platform/ConditionVariable.h index b2cd6289b..fe939671f 100644 --- a/Source/Engine/Platform/ConditionVariable.h +++ b/Source/Engine/Platform/ConditionVariable.h @@ -4,7 +4,7 @@ #if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT #include "Win32/Win32ConditionVariable.h" -#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC +#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS #include "Unix/UnixConditionVariable.h" #elif PLATFORM_SWITCH #include "Platforms/Switch/Engine/Platform/SwitchConditionVariable.h" diff --git a/Source/Engine/Platform/CriticalSection.h b/Source/Engine/Platform/CriticalSection.h index fc72b715e..9f2c724f5 100644 --- a/Source/Engine/Platform/CriticalSection.h +++ b/Source/Engine/Platform/CriticalSection.h @@ -4,7 +4,7 @@ #if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT #include "Win32/Win32CriticalSection.h" -#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC +#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS #include "Unix/UnixCriticalSection.h" #elif PLATFORM_SWITCH #include "Platforms/Switch/Engine/Platform/SwitchCriticalSection.h" diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h index 0506cadc1..22ec5b863 100644 --- a/Source/Engine/Platform/Defines.h +++ b/Source/Engine/Platform/Defines.h @@ -58,6 +58,11 @@ API_ENUM() enum class PlatformType /// Running on Mac. /// Mac = 10, + + /// + /// Running on iPhone. + /// + iOS = 11, }; /// @@ -152,6 +157,8 @@ API_ENUM() enum class ArchitectureType #include "Platforms/Switch/Engine/Platform/SwitchDefines.h" #elif PLATFORM_MAC #include "Mac/MacDefines.h" +#elif PLATFORM_IOS +#include "iOS/iOSDefines.h" #else #error Missing Defines implementation! #endif diff --git a/Source/Engine/Platform/File.h b/Source/Engine/Platform/File.h index d783213e6..12c772064 100644 --- a/Source/Engine/Platform/File.h +++ b/Source/Engine/Platform/File.h @@ -4,7 +4,7 @@ #if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT #include "Win32/Win32File.h" -#elif PLATFORM_LINUX || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC +#elif PLATFORM_LINUX || PLATFORM_PS4 || PLATFORM_PS5 || PLATFORM_MAC || PLATFORM_IOS #include "Unix/UnixFile.h" #elif PLATFORM_ANDROID #include "Android/AndroidFile.h" diff --git a/Source/Engine/Platform/FileSystem.h b/Source/Engine/Platform/FileSystem.h index 235f43ce2..80874a05a 100644 --- a/Source/Engine/Platform/FileSystem.h +++ b/Source/Engine/Platform/FileSystem.h @@ -22,6 +22,8 @@ #include "Platforms/Switch/Engine/Platform/SwitchFileSystem.h" #elif PLATFORM_MAC #include "Mac/MacFileSystem.h" +#elif PLATFORM_IOS +#include "Apple/AppleFileSystem.h" #else #error Missing File System implementation! #endif diff --git a/Source/Engine/Platform/GDK/GDKPlatform.cpp b/Source/Engine/Platform/GDK/GDKPlatform.cpp index 41c309d39..2834888a9 100644 --- a/Source/Engine/Platform/GDK/GDKPlatform.cpp +++ b/Source/Engine/Platform/GDK/GDKPlatform.cpp @@ -555,21 +555,11 @@ struct GetMonitorBoundsData } }; -Rectangle GDKPlatform::GetMonitorBounds(const Float2& screenPos) -{ - return Rectangle(Float2::Zero, GetDesktopSize()); -} - Float2 GDKPlatform::GetDesktopSize() { return Float2(1920, 1080); } -Rectangle GDKPlatform::GetVirtualDesktopBounds() -{ - return Rectangle(Float2::Zero, GetDesktopSize()); -} - void GDKPlatform::GetEnvironmentVariables(Dictionary& result) { const LPWCH environmentStr = GetEnvironmentStringsW(); diff --git a/Source/Engine/Platform/GDK/GDKPlatform.h b/Source/Engine/Platform/GDK/GDKPlatform.h index 28430165f..8d5d44aad 100644 --- a/Source/Engine/Platform/GDK/GDKPlatform.h +++ b/Source/Engine/Platform/GDK/GDKPlatform.h @@ -68,9 +68,7 @@ public: static bool GetHasFocus(); static bool CanOpenUrl(const StringView& url); static void OpenUrl(const StringView& url); - static Rectangle GetMonitorBounds(const Float2& screenPos); static Float2 GetDesktopSize(); - static Rectangle GetVirtualDesktopBounds(); static void GetEnvironmentVariables(Dictionary& result); static bool GetEnvironmentVariable(const String& name, String& value); static bool SetEnvironmentVariable(const String& name, const String& value); diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp index 6da875f9b..5efa1905b 100644 --- a/Source/Engine/Platform/Mac/MacFileSystem.cpp +++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp @@ -3,8 +3,9 @@ #if PLATFORM_MAC #include "MacFileSystem.h" -#include "MacUtils.h" +#include "Engine/Platform/Apple/AppleUtils.h" #include "Engine/Platform/File.h" +#include "Engine/Platform/Platform.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/TimeSpan.h" @@ -22,13 +23,11 @@ #include #include -const DateTime UnixEpoch(1970, 1, 1); - void InitMacDialog(NSSavePanel* dialog, const StringView& initialDirectory, const StringView& filter, const StringView& title) { if (initialDirectory.HasChars()) { - [dialog setDirectoryURL:[NSURL fileURLWithPath:(NSString*)MacUtils::ToString(initialDirectory) isDirectory:YES]]; + [dialog setDirectoryURL:[NSURL fileURLWithPath:(NSString*)AppleUtils::ToString(initialDirectory) isDirectory:YES]]; } if (filter.HasChars()) { @@ -41,13 +40,13 @@ void InitMacDialog(NSSavePanel* dialog, const StringView& initialDirectory, cons String extension = entries[i]; if (extension.StartsWith(TEXT("*."))) extension = extension.Substring(2); - [fileTypes addObject:(NSString*)MacUtils::ToString(extension)]; + [fileTypes addObject:(NSString*)AppleUtils::ToString(extension)]; } [dialog setAllowedFileTypes:fileTypes];*/ } if (title.HasChars()) { - [dialog setMessage:(NSString*)MacUtils::ToString(title)]; + [dialog setMessage:(NSString*)AppleUtils::ToString(title)]; } } @@ -69,12 +68,12 @@ bool MacFileSystem::ShowOpenFileDialog(Window* parentWindow, const StringView& i { const NSArray* urls = [dialog URLs]; for (int32 i = 0; i < [urls count]; i++) - filenames.Add(MacUtils::ToString((CFStringRef)[[urls objectAtIndex:i] path])); + filenames.Add(AppleUtils::ToString((CFStringRef)[[urls objectAtIndex:i] path])); } else { const NSURL* url = [dialog URL]; - filenames.Add(MacUtils::ToString((CFStringRef)[url path])); + filenames.Add(AppleUtils::ToString((CFStringRef)[url path])); } result = false; } @@ -97,7 +96,7 @@ bool MacFileSystem::ShowSaveFileDialog(Window* parentWindow, const StringView& i if ([dialog runModal] == NSModalResponseOK) { const NSURL* url = [dialog URL]; - filenames.Add(MacUtils::ToString((CFStringRef)[url path])); + filenames.Add(AppleUtils::ToString((CFStringRef)[url path])); result = false; } @@ -122,7 +121,7 @@ bool MacFileSystem::ShowBrowseFolderDialog(Window* parentWindow, const StringVie if ([dialog runModal] == NSModalResponseOK) { const NSURL* url = [dialog URL]; - path = MacUtils::ToString((CFStringRef)[url path]); + path = AppleUtils::ToString((CFStringRef)[url path]); result = false; } @@ -136,510 +135,4 @@ bool MacFileSystem::ShowFileExplorer(const StringView& path) return Platform::StartProcess(TEXT("open"), String::Format(TEXT("\"{0}\""), path), StringView::Empty) != 0; } -bool MacFileSystem::CreateDirectory(const StringView& path) -{ - const StringAsANSI<> pathAnsi(*path, path.Length()); - - // Skip if already exists - struct stat fileInfo; - if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode)) - { - return false; - } - - // Recursively do it all again for the parent directory, if any - const int32 slashIndex = path.FindLast('/'); - if (slashIndex > 1) - { - if (CreateDirectory(path.Substring(0, slashIndex))) - { - return true; - } - } - - // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now) - return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST; -} - -bool DeletePathTree(const char* path) -{ - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Recursively remove a nested directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - if (DeletePathTree(full_path)) - return true; - continue; - } - - // Remove a file object - if (unlink(full_path) != 0) - return true; - } - - // Remove the devastated directory and close the object of it - if (rmdir(path) != 0) - return true; - - closedir(dir); - - return false; -} - -bool MacFileSystem::DeleteDirectory(const String& path, bool deleteContents) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - if (deleteContents) - return DeletePathTree(pathANSI.Get()); - return rmdir(pathANSI.Get()) != 0; -} - -bool MacFileSystem::DirectoryExists(const StringView& path) -{ - struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - return S_ISDIR(fileInfo.st_mode); - } - return false; -} - -bool MacFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - const StringAsANSI<> searchPatternANSI(searchPattern); - - // Check if use only top directory - if (option == DirectorySearchOption::TopDirectoryOnly) - return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get()); - return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get()); -} - -bool MacFileSystem::GetChildDirectories(Array& results, const String& directory) -{ - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - const StringAsANSI<> pathANSI(*directory, directory.Length()); - const char* path = pathANSI.Get(); - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Check for directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - // Add directory - results.Add(String(full_path)); - } - } - - closedir(dir); - - return false; -} - -bool MacFileSystem::FileExists(const StringView& path) -{ - struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - return S_ISREG(fileInfo.st_mode); - } - return false; -} - -bool MacFileSystem::DeleteFile(const StringView& path) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - return unlink(pathANSI.Get()) == 0; -} - -uint64 MacFileSystem::GetFileSize(const StringView& path) -{ - struct stat fileInfo; - fileInfo.st_size = -1; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - // Check for directories - if (S_ISDIR(fileInfo.st_mode)) - { - fileInfo.st_size = -1; - } - } - return fileInfo.st_size; -} - -bool MacFileSystem::IsReadOnly(const StringView& path) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - if (access(pathANSI.Get(), W_OK) == -1) - { - return errno == EACCES; - } - return false; -} - -bool MacFileSystem::SetReadOnly(const StringView& path, bool isReadOnly) -{ - const StringAsANSI<> pathANSI(*path, path.Length()); - struct stat fileInfo; - if (stat(pathANSI.Get(), &fileInfo) != -1) - { - if (isReadOnly) - { - fileInfo.st_mode &= ~S_IWUSR; - } - else - { - fileInfo.st_mode |= S_IWUSR; - } - return chmod(pathANSI.Get(), fileInfo.st_mode) == 0; - } - return false; -} - -bool MacFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite) -{ - if (!overwrite && FileExists(dst)) - { - // Already exists - return true; - } - - if (overwrite) - { - unlink(StringAsANSI<>(*dst, dst.Length()).Get()); - } - if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0) - { - if (errno == EXDEV) - { - if (!CopyFile(dst, src)) - { - unlink(StringAsANSI<>(*src, src.Length()).Get()); - return false; - } - } - return true; - } - return false; -} - -bool MacFileSystem::CopyFile(const StringView& dst, const StringView& src) -{ - const StringAsANSI<> srcANSI(*src, src.Length()); - const StringAsANSI<> dstANSI(*dst, dst.Length()); - - int srcFile, dstFile; - char buffer[4096]; - ssize_t readSize; - int cachedError; - - srcFile = open(srcANSI.Get(), O_RDONLY); - if (srcFile < 0) - return true; - dstFile = open(dstANSI.Get(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (dstFile < 0) - goto out_error; - - while (readSize = read(srcFile, buffer, sizeof(buffer)), readSize > 0) - { - char* ptr = buffer; - ssize_t writeSize; - - do - { - writeSize = write(dstFile, ptr, readSize); - if (writeSize >= 0) - { - readSize -= writeSize; - ptr += writeSize; - } - else if (errno != EINTR) - { - goto out_error; - } - } while (readSize > 0); - } - - if (readSize == 0) - { - if (close(dstFile) < 0) - { - dstFile = -1; - goto out_error; - } - close(srcFile); - - // Success - return false; - } - -out_error: - cachedError = errno; - close(srcFile); - if (dstFile >= 0) - close(dstFile); - errno = cachedError; - return true; -} - -bool MacFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern) -{ - size_t pathLength; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - DIR* dir = opendir(path); - if (dir == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char fullPath[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath)); - strcpy(fullPath, path); - strcat(fullPath, "/"); - strcat(fullPath, entry->d_name); - - // Stat for the entry - stat(fullPath, &statEntry); - - // Check for file - if (S_ISREG(statEntry.st_mode) != 0) - { - // Validate with filter - const int32 fullPathLength = StringUtils::Length(fullPath); - const int32 searchPatternLength = StringUtils::Length(searchPattern); - if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0) - { - // All files - } - else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0) - { - // Path ending - } - else - { - // TODO: implement all cases in a generic way - continue; - } - - // Add file - results.Add(String(fullPath)); - } - } - - closedir(dir); - - return false; -} - -bool MacFileSystem::getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern) -{ - // Find all files in this directory - getFilesFromDirectoryTop(results, path, searchPattern); - - size_t pathLength; - DIR* dir; - struct stat statPath, statEntry; - struct dirent* entry; - - // Stat for the path - stat(path, &statPath); - - // If path does not exists or is not dir - exit with status -1 - if (S_ISDIR(statPath.st_mode) == 0) - { - // Is not directory - return true; - } - - // If not possible to read the directory for this user - if ((dir = opendir(path)) == NULL) - { - // Cannot open directory - return true; - } - - // The length of the path - pathLength = strlen(path); - - // Iteration through entries in the directory - while ((entry = readdir(dir)) != NULL) - { - // Skip entries "." and ".." - if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) - continue; - - // Determinate a full path of an entry - char full_path[256]; - ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path)); - strcpy(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - - // Stat for the entry - stat(full_path, &statEntry); - - // Check for directory - if (S_ISDIR(statEntry.st_mode) != 0) - { - if (getFilesFromDirectoryAll(results, full_path, searchPattern)) - { - closedir(dir); - return true; - } - } - } - - closedir(dir); - - return false; -} - -DateTime MacFileSystem::GetFileLastEditTime(const StringView& path) -{ - struct stat fileInfo; - const StringAsANSI<> pathANSI(*path, path.Length()); - if (stat(pathANSI.Get(), &fileInfo) == -1) - { - return DateTime::MinValue(); - } - - const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime); - return UnixEpoch + timeSinceEpoch; -} - -void MacFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result) -{ - String home; - Platform::GetEnvironmentVariable(TEXT("HOME"), home); - switch (type) - { - case SpecialFolder::Desktop: - result = home / TEXT("/Desktop"); - break; - case SpecialFolder::Documents: - result = home / TEXT("/Documents"); - break; - case SpecialFolder::Pictures: - result = home / TEXT("/Pictures"); - break; - case SpecialFolder::AppData: - case SpecialFolder::LocalAppData: - result = home / TEXT("/Library/Caches"); - break; - case SpecialFolder::ProgramData: - result = home / TEXT("/Library/Application Support"); - break; - case SpecialFolder::Temporary: - Platform::GetEnvironmentVariable(TEXT("TMPDIR"), result); - break; - } -} - #endif diff --git a/Source/Engine/Platform/Mac/MacFileSystem.h b/Source/Engine/Platform/Mac/MacFileSystem.h index 272c909f9..c72932d2c 100644 --- a/Source/Engine/Platform/Mac/MacFileSystem.h +++ b/Source/Engine/Platform/Mac/MacFileSystem.h @@ -4,53 +4,20 @@ #if PLATFORM_MAC -#include "Engine/Platform/Base/FileSystemBase.h" +#include "../Apple/AppleFileSystem.h" /// /// Mac platform implementation of filesystem service. /// -class FLAXENGINE_API MacFileSystem : public FileSystemBase +class FLAXENGINE_API MacFileSystem : public AppleFileSystem { public: - // [FileSystemBase] + // [AppleFileSystem] static bool ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames); static bool ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array& filenames); static bool ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path); static bool ShowFileExplorer(const StringView& path); - static bool CreateDirectory(const StringView& path); - static bool DeleteDirectory(const String& path, bool deleteContents = true); - static bool DirectoryExists(const StringView& path); - static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option = DirectorySearchOption::AllDirectories); - static bool GetChildDirectories(Array& results, const String& directory); - static bool FileExists(const StringView& path); - static bool DeleteFile(const StringView& path); - static uint64 GetFileSize(const StringView& path); - static bool IsReadOnly(const StringView& path); - static bool SetReadOnly(const StringView& path, bool isReadOnly); - static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false); - static bool CopyFile(const StringView& dst, const StringView& src); - -public: - - /// - /// Gets last time when file has been modified (in UTC). - /// - /// The file path to check. - /// The last write time or DateTime::MinValue() if cannot get data. - static DateTime GetFileLastEditTime(const StringView& path); - - /// - /// Gets the special folder path. - /// - /// The folder type. - /// The result full path. - static void GetSpecialFolderPath(const SpecialFolder type, String& result); - -private: - - static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern); - static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern); }; #endif diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp index 6a3cab196..72d8c6952 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.cpp +++ b/Source/Engine/Platform/Mac/MacPlatform.cpp @@ -4,7 +4,7 @@ #include "MacPlatform.h" #include "MacWindow.h" -#include "MacUtils.h" +#include "Engine/Platform/Apple/AppleUtils.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" @@ -17,8 +17,8 @@ #include "Engine/Core/Math/Color32.h" #include "Engine/Platform/CPUInfo.h" #include "Engine/Platform/MemoryStats.h" -#include "Engine/Platform/StringUtils.h" #include "Engine/Platform/MessageBox.h" +#include "Engine/Platform/StringUtils.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Platform/Clipboard.h" #include "Engine/Platform/IGuiData.h" @@ -48,117 +48,16 @@ #include #endif -CPUInfo MacCpu; Guid DeviceId; -String UserLocale, ComputerName; -double SecondsPerCycle; +String ComputerName; NSAutoreleasePool* AutoreleasePool = nullptr; -float MacPlatform::ScreenScale = 1.0f; - -String MacUtils::ToString(CFStringRef str) -{ - if (!str) - return String::Empty; - String result; - const int32 length = CFStringGetLength(str); - if (length > 0) - { - CFRange range = CFRangeMake(0, length); - result.ReserveSpace(length); - CFStringGetBytes(str, range, kCFStringEncodingUTF16LE, '?', false, (uint8*)result.Get(), length * sizeof(Char), nullptr); - } - return result; -} - -CFStringRef MacUtils::ToString(const StringView& str) -{ - return CFStringCreateWithBytes(nullptr, (const UInt8*)str.GetText(), str.Length() * sizeof(Char), kCFStringEncodingUTF16LE, false); -} - -Float2 MacUtils::PosToCoca(const Float2& pos) -{ - // MacOS uses y-coordinate starting at the bottom of the screen - Float2 result = pos;// / MacPlatform::ScreenScale; - result.Y *= -1; - result += GetScreensOrigin(); - return result; -} - -Float2 MacUtils::CocaToPos(const Float2& pos) -{ - // MacOS uses y-coordinate starting at the bottom of the screen - Float2 result = pos;// * MacPlatform::ScreenScale; - result -= GetScreensOrigin(); - result.Y *= -1; - return result;// * MacPlatform::ScreenScale; -} - -Float2 MacUtils::GetScreensOrigin() -{ - Float2 result = Float2::Zero; - NSArray* screenArray = [NSScreen screens]; - for (NSUInteger i = 0; i < [screenArray count]; i++) - { - NSRect rect = [[screenArray objectAtIndex:i] frame]; - Float2 pos(rect.origin.x, rect.origin.y + rect.size.height); - pos *= MacPlatform::ScreenScale; - if (pos.X < result.X) - result.X = pos.X; - if (pos.Y > result.Y) - result.Y = pos.Y; - } - return result; -} - -void MacClipboard::Clear() -{ - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard clearContents]; -} - -void MacClipboard::SetText(const StringView& text) -{ - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard clearContents]; - [pasteboard writeObjects:[NSArray arrayWithObject:(NSString*)MacUtils::ToString(text)]]; -} - -void MacClipboard::SetRawData(const Span& data) -{ -} - -void MacClipboard::SetFiles(const Array& files) -{ -} - -String MacClipboard::GetText() -{ - NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; - NSArray* classes = [NSArray arrayWithObject:[NSString class]]; - NSDictionary* options = [NSDictionary dictionary]; - if (![pasteboard canReadObjectForClasses:classes options:options]) - return String::Empty; - NSArray* objects = [pasteboard readObjectsForClasses:classes options:options]; - return MacUtils::ToString((CFStringRef)[objects objectAtIndex:0]); -} - -Array MacClipboard::GetRawData() -{ - return Array(); -} - -Array MacClipboard::GetFiles() -{ - return Array(); -} - DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) { if (CommandLine::Options.Headless) return DialogResult::None; - CFStringRef textRef = MacUtils::ToString(text); - CFStringRef captionRef = MacUtils::ToString(caption); + CFStringRef textRef = AppleUtils::ToString(text); + CFStringRef captionRef = AppleUtils::ToString(caption); CFOptionFlags flags = 0; switch (buttons) { @@ -189,6 +88,83 @@ DialogResult MessageBox::Show(Window* parent, const StringView& text, const Stri return DialogResult::OK; } +Float2 AppleUtils::PosToCoca(const Float2& pos) +{ + // MacOS uses y-coordinate starting at the bottom of the screen + Float2 result = pos;// / ApplePlatform::ScreenScale; + result.Y *= -1; + result += GetScreensOrigin(); + return result; +} + +Float2 AppleUtils::CocaToPos(const Float2& pos) +{ + // MacOS uses y-coordinate starting at the bottom of the screen + Float2 result = pos;// * ApplePlatform::ScreenScale; + result -= GetScreensOrigin(); + result.Y *= -1; + return result;// * ApplePlatform::ScreenScale; +} + +Float2 AppleUtils::GetScreensOrigin() +{ + Float2 result = Float2::Zero; + NSArray* screenArray = [NSScreen screens]; + for (NSUInteger i = 0; i < [screenArray count]; i++) + { + NSRect rect = [[screenArray objectAtIndex:i] frame]; + Float2 pos(rect.origin.x, rect.origin.y + rect.size.height); + pos *= ApplePlatform::ScreenScale; + if (pos.X < result.X) + result.X = pos.X; + if (pos.Y > result.Y) + result.Y = pos.Y; + } + return result; +} + +void MacClipboard::Clear() +{ + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; +} + +void MacClipboard::SetText(const StringView& text) +{ + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard writeObjects:[NSArray arrayWithObject:(NSString*)AppleUtils::ToString(text)]]; +} + +void MacClipboard::SetRawData(const Span& data) +{ +} + +void MacClipboard::SetFiles(const Array& files) +{ +} + +String MacClipboard::GetText() +{ + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSArray* classes = [NSArray arrayWithObject:[NSString class]]; + NSDictionary* options = [NSDictionary dictionary]; + if (![pasteboard canReadObjectForClasses:classes options:options]) + return String::Empty; + NSArray* objects = [pasteboard readObjectsForClasses:classes options:options]; + return AppleUtils::ToString((CFStringRef)[objects objectAtIndex:0]); +} + +Array MacClipboard::GetRawData() +{ + return Array(); +} + +Array MacClipboard::GetFiles() +{ + return Array(); +} + class MacKeyboard : public Keyboard { public: @@ -217,215 +193,28 @@ public: } }; -typedef uint16_t offset_t; -#define align_mem_up(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) - -bool MacPlatform::Is64BitPlatform() -{ - return PLATFORM_64BITS; -} - -CPUInfo MacPlatform::GetCPUInfo() -{ - return MacCpu; -} - -int32 MacPlatform::GetCacheLineSize() -{ - return MacCpu.CacheLineSize; -} - -MemoryStats MacPlatform::GetMemoryStats() -{ - MemoryStats result; - int64 value64; - size_t value64Size = sizeof(value64); - if (sysctlbyname("hw.memsize", &value64, &value64Size, nullptr, 0) != 0) - value64 = 1024 * 1024; - result.TotalPhysicalMemory = value64; - int id[] = { CTL_HW, HW_MEMSIZE }; - if (sysctl(id, 2, &value64, &value64Size, nullptr, 0) != 0) - value64Size = 1024; - result.UsedPhysicalMemory = value64Size; - xsw_usage swapusage; - size_t swapusageSize = sizeof(swapusage); - result.TotalVirtualMemory = result.TotalPhysicalMemory; - result.UsedVirtualMemory = result.UsedPhysicalMemory; - if (sysctlbyname("vm.swapusage", &swapusage, &swapusageSize, nullptr, 0) == 0) - { - result.TotalVirtualMemory += swapusage.xsu_total; - result.UsedVirtualMemory += swapusage.xsu_used; - } - return result; -} - -ProcessMemoryStats MacPlatform::GetProcessMemoryStats() -{ - ProcessMemoryStats result; - result.UsedPhysicalMemory = 1024; - result.UsedVirtualMemory = 1024; - return result; -} - -uint64 MacPlatform::GetCurrentThreadID() -{ - return (uint64)pthread_mach_thread_np(pthread_self()); -} - -void MacPlatform::SetThreadPriority(ThreadPriority priority) -{ - // TODO: impl this -} - -void MacPlatform::SetThreadAffinityMask(uint64 affinityMask) -{ - // TODO: impl this -} - -void MacPlatform::Sleep(int32 milliseconds) -{ - usleep(milliseconds * 1000); -} - -double MacPlatform::GetTimeSeconds() -{ - return SecondsPerCycle * mach_absolute_time(); -} - -uint64 MacPlatform::GetTimeCycles() -{ - return mach_absolute_time(); -} - -uint64 MacPlatform::GetClockFrequency() -{ - return (uint64)(1.0 / SecondsPerCycle); -} - -void MacPlatform::GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond) -{ - // Query for calendar time - struct timeval time; - gettimeofday(&time, nullptr); - - // Convert to local time - struct tm localTime; - localtime_r(&time.tv_sec, &localTime); - - // Extract time - year = localTime.tm_year + 1900; - month = localTime.tm_mon + 1; - dayOfWeek = localTime.tm_wday; - day = localTime.tm_mday; - hour = localTime.tm_hour; - minute = localTime.tm_min; - second = localTime.tm_sec; - millisecond = time.tv_usec / 1000; -} - -void MacPlatform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond) -{ - // Get the calendar time - struct timeval time; - gettimeofday(&time, nullptr); - - // Convert to UTC time - struct tm localTime; - gmtime_r(&time.tv_sec, &localTime); - - // Extract time - year = localTime.tm_year + 1900; - month = localTime.tm_mon + 1; - dayOfWeek = localTime.tm_wday; - day = localTime.tm_mday; - hour = localTime.tm_hour; - minute = localTime.tm_min; - second = localTime.tm_sec; - millisecond = time.tv_usec / 1000; -} - bool MacPlatform::Init() { - if (UnixPlatform::Init()) + if (ApplePlatform::Init()) return true; - // Init timing - { - mach_timebase_info_data_t info; - mach_timebase_info(&info); - SecondsPerCycle = 1e-9 * (double)info.numer / (double)info.denom; - } - - // Get CPU info - int32 value32; - int64 value64; - size_t value32Size = sizeof(value32), value64Size = sizeof(value64); - if (sysctlbyname("hw.packages", &value32, &value32Size, nullptr, 0) != 0) - value32 = 1; - MacCpu.ProcessorPackageCount = value32; - if (sysctlbyname("hw.physicalcpu", &value32, &value32Size, nullptr, 0) != 0) - value32 = 1; - MacCpu.ProcessorCoreCount = value32; - if (sysctlbyname("hw.logicalcpu", &value32, &value32Size, nullptr, 0) != 0) - value32 = 1; - MacCpu.LogicalProcessorCount = value32; - if (sysctlbyname("hw.l1icachesize", &value32, &value32Size, nullptr, 0) != 0) - value32 = 0; - MacCpu.L1CacheSize = value32; - if (sysctlbyname("hw.l2cachesize", &value32, &value32Size, nullptr, 0) != 0) - value32 = 0; - MacCpu.L2CacheSize = value32; - if (sysctlbyname("hw.l3cachesize", &value32, &value32Size, nullptr, 0) != 0) - value32 = 0; - MacCpu.L3CacheSize = value32; - if (sysctlbyname("hw.pagesize", &value32, &value32Size, nullptr, 0) != 0) - value32 = vm_page_size; - MacCpu.PageSize = value32; - if (sysctlbyname("hw.cpufrequency_max", &value64, &value64Size, nullptr, 0) != 0) - value64 = GetClockFrequency(); - MacCpu.ClockSpeed = value64; - if (sysctlbyname("hw.cachelinesize", &value32, &value32Size, nullptr, 0) != 0) - value32 = PLATFORM_CACHE_LINE_SIZE; - MacCpu.CacheLineSize = value32; - // Get device id { io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/"); CFStringRef deviceUuid = (CFStringRef)IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0); IOObjectRelease(ioRegistryRoot); - String uuidStr = MacUtils::ToString(deviceUuid); + String uuidStr = AppleUtils::ToString(deviceUuid); Guid::Parse(uuidStr, DeviceId); CFRelease(deviceUuid); } - // Get locale - { - CFLocaleRef locale = CFLocaleCopyCurrent(); - CFStringRef localeLang = (CFStringRef)CFLocaleGetValue(locale, kCFLocaleLanguageCode); - CFStringRef localeCountry = (CFStringRef)CFLocaleGetValue(locale, kCFLocaleCountryCode); - UserLocale = MacUtils::ToString(localeLang); - String localeCountryStr = MacUtils::ToString(localeCountry); - if (localeCountryStr.HasChars()) - UserLocale += TEXT("-") + localeCountryStr; - CFRelease(locale); - CFRelease(localeLang); - CFRelease(localeCountry); - } - // Get computer name { CFStringRef computerName = SCDynamicStoreCopyComputerName(nullptr, nullptr); - ComputerName = MacUtils::ToString(computerName); + ComputerName = AppleUtils::ToString(computerName); CFRelease(computerName); } - // Init user - { - String username; - GetEnvironmentVariable(TEXT("USER"), username); - OnPlatformUserAdd(New(username)); - } - // Find the maximum scale of the display to handle high-dpi displays scaling factor { NSArray* screenArray = [NSScreen screens]; @@ -442,10 +231,12 @@ bool MacPlatform::Init() // Init application [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + AutoreleasePool = [[NSAutoreleasePool alloc] init]; + + // Init main menu NSMenu* mainMenu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; [NSApp setMainMenu:mainMenu]; // TODO: expose main menu for app (eg. to be used by Game or Editor on macOS-only) - AutoreleasePool = [[NSAutoreleasePool alloc] init]; Input::Mouse = New(); Input::Keyboard = New(); @@ -455,7 +246,7 @@ bool MacPlatform::Init() void MacPlatform::LogInfo() { - UnixPlatform::LogInfo(); + ApplePlatform::LogInfo(); char str[250]; size_t strSize = sizeof(str); @@ -488,24 +279,6 @@ void MacPlatform::Tick() AutoreleasePool = [[NSAutoreleasePool alloc] init]; } -void MacPlatform::BeforeExit() -{ -} - -void MacPlatform::Exit() -{ -} - -void MacPlatform::SetHighDpiAwarenessEnabled(bool enable) -{ - // Disable resolution scaling in low dpi mode - if (!enable) - { - CustomDpiScale /= ScreenScale; - ScreenScale = 1.0f; - } -} - int32 MacPlatform::GetDpi() { CGDirectDisplayID mainDisplay = CGMainDisplayID(); @@ -515,9 +288,9 @@ int32 MacPlatform::GetDpi() return Math::Max(dpi, 72.0f); } -String MacPlatform::GetUserLocaleName() +Guid MacPlatform::GetUniqueDeviceId() { - return UserLocale; + return DeviceId; } String MacPlatform::GetComputerName() @@ -525,40 +298,6 @@ String MacPlatform::GetComputerName() return ComputerName; } -bool MacPlatform::GetHasFocus() -{ - // Check if any window is focused - ScopeLock lock(WindowsManager::WindowsLocker); - for (auto window : WindowsManager::Windows) - { - if (window->IsFocused()) - return true; - } - - // Default to true if has no windows open - return WindowsManager::Windows.IsEmpty(); -} - -void MacPlatform::CreateGuid(Guid& result) -{ - uuid_t uuid; - uuid_generate(uuid); - auto ptr = (uint32*)&uuid; - result.A = ptr[0]; - result.B = ptr[1]; - result.C = ptr[2]; - result.D = ptr[3]; -} - -bool MacPlatform::CanOpenUrl(const StringView& url) -{ - return false; -} - -void MacPlatform::OpenUrl(const StringView& url) -{ -} - Float2 MacPlatform::GetMousePosition() { CGEventRef event = CGEventCreate(nullptr); @@ -584,7 +323,7 @@ Float2 MacPlatform::GetDesktopSize() Rectangle GetDisplayBounds(CGDirectDisplayID display) { CGRect rect = CGDisplayBounds(display); - float screnScale = MacPlatform::ScreenScale; + float screnScale = ApplePlatform::ScreenScale; return Rectangle(rect.origin.x * screnScale, rect.origin.y * screnScale, rect.size.width * screnScale, rect.size.height * screnScale); } @@ -625,54 +364,11 @@ String MacPlatform::GetMainDirectory() return path; } -String MacPlatform::GetExecutableFilePath() -{ - char buf[PATH_MAX]; - uint32 size = PATH_MAX; - String result; - if (_NSGetExecutablePath(buf, &size) == 0) - result.SetUTF8(buf, StringUtils::Length(buf)); - return result; -} - -Guid MacPlatform::GetUniqueDeviceId() -{ - return DeviceId; -} - -String MacPlatform::GetWorkingDirectory() -{ - char buffer[256]; - getcwd(buffer, ARRAY_COUNT(buffer)); - return String(buffer); -} - -bool MacPlatform::SetWorkingDirectory(const String& path) -{ - return chdir(StringAsANSI<>(*path).Get()) != 0; -} - Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings) { return New(settings); } -bool MacPlatform::GetEnvironmentVariable(const String& name, String& value) -{ - char* env = getenv(StringAsANSI<>(*name).Get()); - if (env) - { - value = String(env); - return false; - } - return true; -} - -bool MacPlatform::SetEnvironmentVariable(const String& name, const String& value) -{ - return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0; -} - int32 MacProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& environment, bool waitForEnd, bool logOutput) { LOG(Info, "Command: {0}", cmdLine); @@ -731,7 +427,7 @@ int32 MacPlatform::StartProcess(const StringView& filename, const StringView& ar // Special case if filename points to the app package (use actual executable) String exePath = filename; { - NSString* processPath = (NSString*)MacUtils::ToString(filename); + NSString* processPath = (NSString*)AppleUtils::ToString(filename); if (![[NSFileManager defaultManager] fileExistsAtPath: processPath]) { NSString* appName = [[processPath lastPathComponent] stringByDeletingPathExtension]; @@ -746,7 +442,7 @@ int32 MacPlatform::StartProcess(const StringView& filename, const StringView& ar { processPath = [bundle executablePath]; if (processPath != nil) - exePath = MacUtils::ToString((CFStringRef)processPath); + exePath = AppleUtils::ToString((CFStringRef)processPath); } } } @@ -766,57 +462,4 @@ int32 MacPlatform::RunProcess(const StringView& cmdLine, const StringView& worki return MacProcess(cmdLine, workingDir, environment, true, true); } -void* MacPlatform::LoadLibrary(const Char* filename) -{ - const StringAsANSI<> filenameANSI(filename); - void* result = dlopen(filenameANSI.Get(), RTLD_LAZY | RTLD_LOCAL); - if (!result) - { - LOG(Error, "Failed to load {0} because {1}", filename, String(dlerror())); - } - return result; -} - -void MacPlatform::FreeLibrary(void* handle) -{ - dlclose(handle); -} - -void* MacPlatform::GetProcAddress(void* handle, const char* symbol) -{ - return dlsym(handle, symbol); -} - -Array MacPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context) -{ - Array result; -#if CRASH_LOG_ENABLE - void* callstack[120]; - skipCount = Math::Min(skipCount, ARRAY_COUNT(callstack)); - int32 maxCount = Math::Min(ARRAY_COUNT(callstack), skipCount + maxDepth); - int32 count = backtrace(callstack, maxCount); - int32 useCount = count - skipCount; - if (useCount > 0) - { - char** names = backtrace_symbols(callstack + skipCount, useCount); - result.Resize(useCount); - for (int32 i = 0; i < useCount; i++) - { - char* name = names[i]; - StackFrame& frame = result[i]; - frame.ProgramCounter = callstack[skipCount + i]; - frame.ModuleName[0] = 0; - frame.FileName[0] = 0; - frame.LineNumber = 0; - int32 nameLen = Math::Min(StringUtils::Length(name), ARRAY_COUNT(frame.FunctionName) - 1); - Platform::MemoryCopy(frame.FunctionName, name, nameLen); - frame.FunctionName[nameLen] = 0; - - } - free(names); - } -#endif - return result; -} - #endif diff --git a/Source/Engine/Platform/Mac/MacPlatform.h b/Source/Engine/Platform/Mac/MacPlatform.h index 074fb3871..98fa48c67 100644 --- a/Source/Engine/Platform/Mac/MacPlatform.h +++ b/Source/Engine/Platform/Mac/MacPlatform.h @@ -4,115 +4,33 @@ #if PLATFORM_MAC -#include "../Unix/UnixPlatform.h" +#include "../Apple/ApplePlatform.h" /// /// The Mac platform implementation and application management utilities. /// -class FLAXENGINE_API MacPlatform : public UnixPlatform +class FLAXENGINE_API MacPlatform : public ApplePlatform { public: - static float ScreenScale; -public: - - // [UnixPlatform] - FORCE_INLINE static void MemoryBarrier() - { - __sync_synchronize(); - } - FORCE_INLINE static int64 InterlockedExchange(int64 volatile* dst, int64 exchange) - { - return __sync_lock_test_and_set(dst, exchange); - } - FORCE_INLINE static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand) - { - return __sync_val_compare_and_swap(dst, comperand, exchange); - } - FORCE_INLINE static int64 InterlockedCompareExchange(int64 volatile* dst, int64 exchange, int64 comperand) - { - return __sync_val_compare_and_swap(dst, comperand, exchange); - } - FORCE_INLINE static int64 InterlockedIncrement(int64 volatile* dst) - { - return __sync_add_and_fetch(dst, 1); - } - FORCE_INLINE static int64 InterlockedDecrement(int64 volatile* dst) - { - return __sync_sub_and_fetch(dst, 1); - } - FORCE_INLINE static int64 InterlockedAdd(int64 volatile* dst, int64 value) - { - return __sync_fetch_and_add(dst, value); - } - FORCE_INLINE static int32 AtomicRead(int32 volatile* dst) - { - return __atomic_load_n(dst, __ATOMIC_RELAXED); - } - FORCE_INLINE static int64 AtomicRead(int64 volatile* dst) - { - return __atomic_load_n(dst, __ATOMIC_RELAXED); - } - FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value) - { - __atomic_store(dst, &value, __ATOMIC_SEQ_CST); - } - FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value) - { - __atomic_store(dst, &value, __ATOMIC_SEQ_CST); - } - FORCE_INLINE static void Prefetch(void const* ptr) - { - __builtin_prefetch(static_cast(ptr)); - } - static bool Is64BitPlatform(); - static CPUInfo GetCPUInfo(); - static int32 GetCacheLineSize(); - static MemoryStats GetMemoryStats(); - static ProcessMemoryStats GetProcessMemoryStats(); - static uint64 GetCurrentThreadID(); - static void SetThreadPriority(ThreadPriority priority); - static void SetThreadAffinityMask(uint64 affinityMask); - static void Sleep(int32 milliseconds); - static double GetTimeSeconds(); - static uint64 GetTimeCycles(); - static uint64 GetClockFrequency(); - static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); - static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond); + // [ApplePlatform] static bool Init(); static void LogInfo(); static void BeforeRun(); static void Tick(); - static void BeforeExit(); - static void Exit(); - static void SetHighDpiAwarenessEnabled(bool enable); static int32 GetDpi(); - static String GetUserLocaleName(); + static Guid GetUniqueDeviceId(); static String GetComputerName(); - static bool GetHasFocus(); - static void CreateGuid(Guid& result); - static bool CanOpenUrl(const StringView& url); - static void OpenUrl(const StringView& url); static Float2 GetMousePosition(); static void SetMousePosition(const Float2& pos); - static Rectangle GetMonitorBounds(const Float2& screenPos); - static Float2 GetDesktopSize(); - static Rectangle GetVirtualDesktopBounds(); + static Rectangle GetMonitorBounds(const Float2& screenPos); + static Float2 GetDesktopSize(); + static Rectangle GetVirtualDesktopBounds(); static String GetMainDirectory(); - static String GetExecutableFilePath(); - static Guid GetUniqueDeviceId(); - static String GetWorkingDirectory(); - static bool SetWorkingDirectory(const String& path); static Window* CreateWindow(const CreateWindowSettings& settings); - static bool GetEnvironmentVariable(const String& name, String& value); - static bool SetEnvironmentVariable(const String& name, const String& value); static int32 StartProcess(const StringView& filename, const StringView& args, const StringView& workingDir, bool hiddenWindow = false, bool waitForEnd = false); static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, bool hiddenWindow = true); static int32 RunProcess(const StringView& cmdLine, const StringView& workingDir, const Dictionary& environment, bool hiddenWindow = true); - static void* LoadLibrary(const Char* filename); - static void FreeLibrary(void* handle); - static void* GetProcAddress(void* handle, const char* symbol); - static Array GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr); }; #endif diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp index 3f88eb9b9..fe99b4fdd 100644 --- a/Source/Engine/Platform/Mac/MacWindow.cpp +++ b/Source/Engine/Platform/Mac/MacWindow.cpp @@ -3,7 +3,7 @@ #if PLATFORM_MAC #include "../Window.h" -#include "MacUtils.h" +#include "Engine/Platform/Apple/AppleUtils.h" #include "Engine/Platform/IGuiData.h" #include "Engine/Core/Log.h" #include "Engine/Input/Input.h" @@ -208,7 +208,7 @@ void GetDragDropData(const MacWindow* window, id sender, Float2& if ([[pasteboard types] containsObject:NSPasteboardTypeString]) { dropData.CurrentType = IGuiData::Type::Text; - dropData.AsText = MacUtils::ToString((CFStringRef)[pasteboard stringForType:NSPasteboardTypeString]); + dropData.AsText = AppleUtils::ToString((CFStringRef)[pasteboard stringForType:NSPasteboardTypeString]); } else { @@ -218,7 +218,7 @@ void GetDragDropData(const MacWindow* window, id sender, Float2& { NSString* url = [[files objectAtIndex:i] path]; NSString* file = [NSURL URLWithString:url].path; - dropData.AsFiles.Add(MacUtils::ToString((CFStringRef)file)); + dropData.AsFiles.Add(AppleUtils::ToString((CFStringRef)file)); } } } @@ -572,7 +572,7 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) : WindowBase(settings) { _clientSize = Float2(settings.Size.X, settings.Size.Y); - Float2 pos = MacUtils::PosToCoca(settings.Position); + Float2 pos = AppleUtils::PosToCoca(settings.Position); NSRect frame = NSMakeRect(pos.X, pos.Y - settings.Size.Y, settings.Size.X, settings.Size.Y); NSUInteger styleMask = NSWindowStyleMaskClosable; if (settings.IsRegularWindow) @@ -606,7 +606,7 @@ MacWindow::MacWindow(const CreateWindowSettings& settings) MacViewImpl* view = [[MacViewImpl alloc] init]; view.wantsLayer = YES; [view setWindow:this]; - window.title = (__bridge NSString*)MacUtils::ToString(settings.Title); + window.title = (__bridge NSString*)AppleUtils::ToString(settings.Title); [window setWindow:this]; [window setReleasedWhenClosed:NO]; [window setMinSize:NSMakeSize(settings.MinimumSize.X, settings.MinimumSize.Y)]; @@ -779,7 +779,7 @@ void MacWindow::SetClientBounds(const Rectangle& clientArea) //newRect.origin.x = oldRect.origin.x; //newRect.origin.y = NSMaxY(oldRect) - newRect.size.height; - Float2 pos = MacUtils::PosToCoca(clientArea.Location); + Float2 pos = AppleUtils::PosToCoca(clientArea.Location); Float2 titleSize = GetWindowTitleSize(this); newRect.origin.x = pos.X + titleSize.X; newRect.origin.y = pos.Y - newRect.size.height + titleSize.Y; @@ -792,7 +792,7 @@ void MacWindow::SetPosition(const Float2& position) NSWindow* window = (NSWindow*)_window; if (!window) return; - Float2 pos = MacUtils::PosToCoca(position) / MacPlatform::ScreenScale; + Float2 pos = AppleUtils::PosToCoca(position) / MacPlatform::ScreenScale; NSRect rect = [window frame]; [window setFrameOrigin:NSMakePoint(pos.X, pos.Y - rect.size.height)]; } @@ -803,7 +803,7 @@ Float2 MacWindow::GetPosition() const if (!window) return Float2::Zero; NSRect rect = [window frame]; - return MacUtils::CocaToPos(Float2(rect.origin.x, rect.origin.y + rect.size.height) * MacPlatform::ScreenScale); + return AppleUtils::CocaToPos(Float2(rect.origin.x, rect.origin.y + rect.size.height) * MacPlatform::ScreenScale); } Float2 MacWindow::GetSize() const @@ -868,7 +868,7 @@ void MacWindow::SetTitle(const StringView& title) NSWindow* window = (NSWindow*)_window; if (!window) return; - [window setTitle:(__bridge NSString*)MacUtils::ToString(_title)]; + [window setTitle:(__bridge NSString*)AppleUtils::ToString(_title)]; } DragDropEffect MacWindow::DoDragDrop(const StringView& data) diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h index fda650304..e41d3c7e4 100644 --- a/Source/Engine/Platform/Network.h +++ b/Source/Engine/Platform/Network.h @@ -4,7 +4,7 @@ #if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT #include "Win32/Win32Network.h" -#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_MAC +#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_MAC || PLATFORM_IOS #include "Unix/UnixNetwork.h" #elif PLATFORM_PS4 #include "Platforms/PS4/Engine/Platform/PS4Network.h" @@ -12,8 +12,6 @@ #include "Platforms/PS5/Engine/Platform/PS5Network.h" #elif PLATFORM_SWITCH #include "Platforms/Switch/Engine/Platform/SwitchNetwork.h" -#elif PLATFORM_MAC -#include "Mac/MacNetwork.h" #else #error Missing Network implementation! #endif diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs index bfdd07d6f..ef88e9bcf 100644 --- a/Source/Engine/Platform/Platform.Build.cs +++ b/Source/Engine/Platform/Platform.Build.cs @@ -79,8 +79,14 @@ public class Platform : EngineModule break; case TargetPlatform.Mac: options.SourcePaths.Add(Path.Combine(FolderPath, "Unix")); + options.SourcePaths.Add(Path.Combine(FolderPath, "Apple")); options.SourcePaths.Add(Path.Combine(FolderPath, "Mac")); break; + case TargetPlatform.iOS: + options.SourcePaths.Add(Path.Combine(FolderPath, "Unix")); + options.SourcePaths.Add(Path.Combine(FolderPath, "Apple")); + options.SourcePaths.Add(Path.Combine(FolderPath, "iOS")); + break; default: throw new InvalidPlatformException(options.Platform.Target); } if (options.Target.IsEditor) @@ -92,6 +98,7 @@ public class Platform : EngineModule options.SourceFiles.Add(Path.Combine(FolderPath, "Android", "AndroidPlatformSettings.h")); options.SourceFiles.Add(Path.Combine(FolderPath, "GDK", "GDKPlatformSettings.h")); options.SourceFiles.Add(Path.Combine(FolderPath, "Mac", "MacPlatformSettings.h")); + options.SourceFiles.Add(Path.Combine(FolderPath, "iOS", "iOSPlatformSettings.h")); AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxOne", "Engine", "Platform", "XboxOnePlatformSettings.h")); AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform", "XboxScarlettPlatformSettings.h")); AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Platform", "PS4PlatformSettings.h")); diff --git a/Source/Engine/Platform/Platform.h b/Source/Engine/Platform/Platform.h index 4d9e090f8..986aeec90 100644 --- a/Source/Engine/Platform/Platform.h +++ b/Source/Engine/Platform/Platform.h @@ -28,6 +28,8 @@ #include "Platforms/Switch/Engine/Platform/SwitchPlatform.h" #elif PLATFORM_MAC #include "Mac/MacPlatform.h" +#elif PLATFORM_IOS +#include "iOS/iOSPlatform.h" #else #error Missing Platform implementation! #endif diff --git a/Source/Engine/Platform/Thread.h b/Source/Engine/Platform/Thread.h index 1cf585014..be85b36f9 100644 --- a/Source/Engine/Platform/Thread.h +++ b/Source/Engine/Platform/Thread.h @@ -14,8 +14,8 @@ #include "Android/AndroidThread.h" #elif PLATFORM_SWITCH #include "Platforms/Switch/Engine/Platform/SwitchThread.h" -#elif PLATFORM_MAC -#include "Mac/MacThread.h" +#elif PLATFORM_MAC || PLATFORM_IOS +#include "Apple/AppleThread.h" #else #error Missing Thread implementation! #endif diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index cb218ddf0..4eff07b18 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -243,8 +243,8 @@ class UnixFile; typedef UnixFile File; class MacPlatform; typedef MacPlatform Platform; -class MacThread; -typedef MacThread Thread; +class AppleThread; +typedef AppleThread Thread; class MacWindow; typedef MacWindow Window; class UnixNetwork; @@ -252,6 +252,31 @@ typedef UnixNetwork Network; class UserBase; typedef UserBase User; +#elif PLATFORM_IOS + +class ClipboardBase; +typedef ClipboardBase Clipboard; +class UnixCriticalSection; +typedef UnixCriticalSection CriticalSection; +class UnixConditionVariable; +typedef UnixConditionVariable ConditionVariable; +class AppleFileSystem; +typedef AppleFileSystem FileSystem; +class FileSystemWatcherBase; +typedef FileSystemWatcherBase FileSystemWatcher; +class UnixFile; +typedef UnixFile File; +class iOSPlatform; +typedef iOSPlatform Platform; +class AppleThread; +typedef AppleThread Thread; +class iOSWindow; +typedef iOSWindow Window; +class UnixNetwork; +typedef UnixNetwork Network; +class UserBase; +typedef UserBase User; + #else #error Missing Types implementation! diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp index f7c883878..0e9f465b6 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.cpp +++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp @@ -186,18 +186,6 @@ Float2 UWPPlatform::GetDesktopSize() return result; } -Rectangle UWPPlatform::GetMonitorBounds(const Float2& screenPos) -{ - // TODO: do it in a proper way - return Rectangle(Float2::Zero, GetDesktopSize()); -} - -Rectangle UWPPlatform::GetVirtualDesktopBounds() -{ - // TODO: do it in a proper way - return Rectangle(Float2::Zero, GetDesktopSize()); -} - Window* UWPPlatform::CreateWindow(const CreateWindowSettings& settings) { // Settings with provided UWPWindowImpl are only valid diff --git a/Source/Engine/Platform/UWP/UWPPlatform.h b/Source/Engine/Platform/UWP/UWPPlatform.h index be114f419..794a8f97a 100644 --- a/Source/Engine/Platform/UWP/UWPPlatform.h +++ b/Source/Engine/Platform/UWP/UWPPlatform.h @@ -39,9 +39,7 @@ public: static void OpenUrl(const StringView& url); static Float2 GetMousePosition(); static void SetMousePosition(const Float2& pos); - static Rectangle GetMonitorBounds(const Float2& screenPos); static Float2 GetDesktopSize(); - static Rectangle GetVirtualDesktopBounds(); static Window* CreateWindow(const CreateWindowSettings& settings); static void* LoadLibrary(const Char* filename); }; diff --git a/Source/Engine/Platform/Window.h b/Source/Engine/Platform/Window.h index b5b3ff7d2..20acde185 100644 --- a/Source/Engine/Platform/Window.h +++ b/Source/Engine/Platform/Window.h @@ -22,6 +22,8 @@ #include "Platforms/PS5/Engine/Platform/PS5Window.h" #elif PLATFORM_MAC #include "Mac/MacWindow.h" +#elif PLATFORM_IOS +#include "iOS/iOSWindow.h" #else #error Missing Window implementation! #endif diff --git a/Source/Engine/Platform/iOS/iOSDefines.h b/Source/Engine/Platform/iOS/iOSDefines.h new file mode 100644 index 000000000..321607464 --- /dev/null +++ b/Source/Engine/Platform/iOS/iOSDefines.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_IOS + +#include "../Unix/UnixDefines.h" + +// Platform description +#define PLATFORM_TYPE PlatformType::iOS +#define PLATFORM_64BITS 1 +#define PLATFORM_ARCH_ARM64 1 +#define PLATFORM_ARCH ArchitectureType::ARM64 +#define PLATFORM_CACHE_LINE_SIZE 128 +#define PLATFORM_DEBUG_BREAK __builtin_trap() + +#endif diff --git a/Source/Engine/Platform/iOS/iOSPlatform.cpp b/Source/Engine/Platform/iOS/iOSPlatform.cpp new file mode 100644 index 000000000..c327b57e4 --- /dev/null +++ b/Source/Engine/Platform/iOS/iOSPlatform.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#if PLATFORM_IOS + +#include "iOSPlatform.h" +#include "iOSWindow.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Platform/StringUtils.h" +#include "Engine/Platform/MessageBox.h" +#include +#include + +int32 Dpi = 96; +Guid DeviceId; + +DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon) +{ + // TODO: implement message box popup on iOS + return DialogResult::OK; +} + +bool iOSPlatform::Init() +{ + if (ApplePlatform::Init()) + return true; + + ScreenScale = [[UIScreen mainScreen] scale]; + CustomDpiScale *= ScreenScale; + Dpi = 72; // TODO: calculate screen dpi (probably hardcoded map for iPhone model) + + return false; +} + +void iOSPlatform::LogInfo() +{ + ApplePlatform::LogInfo(); + + struct utsname systemInfo; + uname(&systemInfo); + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + LOG(Info, "{3}, iOS {0}.{1}.{2}", version.majorVersion, version.minorVersion, version.patchVersion, String(systemInfo.machine)); +} + +void iOSPlatform::Tick() +{ + // Process system events + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0001, true) == kCFRunLoopRunHandledSource) + { + } +} + +int32 iOSPlatform::GetDpi() +{ + return Dpi; +} + +Guid iOSPlatform::GetUniqueDeviceId() +{ + return Guid::Empty; // TODO: use MAC address of the iPhone to generate device id (at least within network connection state) +} + +String iOSPlatform::GetComputerName() +{ + return TEXT("iPhone"); +} + +Float2 iOSPlatform::GetDesktopSize() +{ + CGRect frame = [[UIScreen mainScreen] bounds]; + float scale = [[UIScreen mainScreen] scale]; + return Float2((float)frame.size.width * scale, (float)frame.size.height * scale); +} + +String iOSPlatform::GetMainDirectory() +{ + String path = StringUtils::GetDirectoryName(GetExecutableFilePath()); + if (path.EndsWith(TEXT("/Contents/iOS"))) + { + // If running from executable in a package, go up to the Contents + path = StringUtils::GetDirectoryName(path); + } + return path; +} + +Window* iOSPlatform::CreateWindow(const CreateWindowSettings& settings) +{ + return New(settings); +} + +#endif diff --git a/Source/Engine/Platform/iOS/iOSPlatform.h b/Source/Engine/Platform/iOS/iOSPlatform.h new file mode 100644 index 000000000..94c299f09 --- /dev/null +++ b/Source/Engine/Platform/iOS/iOSPlatform.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_IOS + +#include "../Apple/ApplePlatform.h" + +/// +/// The iOS platform implementation and application management utilities. +/// +class FLAXENGINE_API iOSPlatform : public ApplePlatform +{ +public: + + // [ApplePlatform] + static bool Init(); + static void LogInfo(); + static void Tick(); + static int32 GetDpi(); + static Guid GetUniqueDeviceId(); + static String GetComputerName(); + static Float2 GetDesktopSize(); + static String GetMainDirectory(); + static Window* CreateWindow(const CreateWindowSettings& settings); +}; + +#endif diff --git a/Source/Engine/Platform/iOS/iOSPlatformSettings.h b/Source/Engine/Platform/iOS/iOSPlatformSettings.h new file mode 100644 index 000000000..59f940d82 --- /dev/null +++ b/Source/Engine/Platform/iOS/iOSPlatformSettings.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_IOS || USE_EDITOR + +#include "Engine/Core/Config/PlatformSettingsBase.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Scripting/SoftObjectReference.h" + +class Texture; + +/// +/// iOS platform settings. +/// +API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API iOSPlatformSettings : public SettingsBase +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(iOSPlatformSettings); + + /// + /// The app identifier (reversed DNS, eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. + /// + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String AppIdentifier = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}"); + + /// + /// Custom icon texture to use for the application (overrides the default one). + /// + API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Other\")") + SoftObjectReference OverrideIcon; + +public: + /// + /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. + /// + static iOSPlatformSettings* Get(); + + // [SettingsBase] + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override + { + DESERIALIZE(AppIdentifier); + DESERIALIZE(OverrideIcon); + } +}; + +#if PLATFORM_IOS +typedef iOSPlatformSettings PlatformSettings; +#endif + +#endif diff --git a/Source/Engine/Platform/iOS/iOSWindow.cpp b/Source/Engine/Platform/iOS/iOSWindow.cpp new file mode 100644 index 000000000..a4d7ec619 --- /dev/null +++ b/Source/Engine/Platform/iOS/iOSWindow.cpp @@ -0,0 +1,212 @@ +// 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 +#include + +@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 setAutoResizeDrawable: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 diff --git a/Source/Engine/Platform/iOS/iOSWindow.h b/Source/Engine/Platform/iOS/iOSWindow.h new file mode 100644 index 000000000..1c934df1e --- /dev/null +++ b/Source/Engine/Platform/iOS/iOSWindow.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_IOS + +#include "Engine/Platform/Base/WindowBase.h" +#include "Engine/Platform/Platform.h" + +/// +/// Implementation of the window class for iOS platform. +/// +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); + +public: + + // [Window] + 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 diff --git a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs index 8bdd1c013..f2e675038 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs +++ b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs @@ -36,6 +36,7 @@ public class TextureTool : EngineModule case TargetPlatform.Android: case TargetPlatform.Switch: case TargetPlatform.Mac: + case TargetPlatform.iOS: useStb = true; break; default: throw new InvalidPlatformException(options.Platform.Target); diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index 41c09687f..385a08fb7 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -786,6 +786,7 @@ void ShaderGenerator::ProcessGroupTools(Box* box, Node* node, Value& value) PLATFORM_CASE(9, "PLATFORM_SWITCH"); PLATFORM_CASE(10, "PLATFORM_PS5"); PLATFORM_CASE(11, "PLATFORM_MAC"); + PLATFORM_CASE(12, "PLATFORM_IOS"); #undef PLATFORM_CASE break; } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 4e067e61c..7a98dcb6e 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -983,6 +983,9 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) case PlatformType::Mac: boxId = 11; break; + case PlatformType::iOS: + boxId = 12; + break; default: ; } value = tryGetValue(node->GetBox(node->GetBox(boxId)->HasConnection() ? boxId : 1), Value::Zero); diff --git a/Source/ThirdParty/enet/enet.h b/Source/ThirdParty/enet/enet.h index 2357e8488..7386d1fa5 100644 --- a/Source/ThirdParty/enet/enet.h +++ b/Source/ThirdParty/enet/enet.h @@ -5002,7 +5002,7 @@ extern "C" { tv->tv_nsec = t.QuadPart % 1000000 * 1000; return (0); } - #elif __APPLE__ && __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 + #elif __APPLE__ && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 #define CLOCK_MONOTONIC 0 int clock_gettime(int X, struct timespec *ts) { diff --git a/Source/ThirdParty/freetype/freetype.Build.cs b/Source/ThirdParty/freetype/freetype.Build.cs index 32c3888f6..b409581fa 100644 --- a/Source/ThirdParty/freetype/freetype.Build.cs +++ b/Source/ThirdParty/freetype/freetype.Build.cs @@ -42,6 +42,7 @@ public class freetype : DepsModule case TargetPlatform.Android: case TargetPlatform.Switch: case TargetPlatform.Mac: + case TargetPlatform.iOS: options.OutputFiles.Add(Path.Combine(depsRoot, "libfreetype.a")); break; default: throw new InvalidPlatformException(options.Platform.Target); diff --git a/Source/ThirdParty/ogg/ogg.Build.cs b/Source/ThirdParty/ogg/ogg.Build.cs index db0e0ae43..0bd514ca7 100644 --- a/Source/ThirdParty/ogg/ogg.Build.cs +++ b/Source/ThirdParty/ogg/ogg.Build.cs @@ -42,6 +42,7 @@ public class ogg : DepsModule case TargetPlatform.Android: case TargetPlatform.Switch: case TargetPlatform.Mac: + case TargetPlatform.iOS: options.OutputFiles.Add(Path.Combine(depsRoot, "libogg.a")); break; default: throw new InvalidPlatformException(options.Platform.Target); diff --git a/Source/ThirdParty/volk/volk.Build.cs b/Source/ThirdParty/volk/volk.Build.cs index 0b5e4d1c0..2f4cfa0d6 100644 --- a/Source/ThirdParty/volk/volk.Build.cs +++ b/Source/ThirdParty/volk/volk.Build.cs @@ -44,6 +44,11 @@ public class volk : ThirdPartyModule options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/macOS/libMoltenVK.dylib")); options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/macOS/MoltenVK_icd.json")); break; + case TargetPlatform.iOS: + options.PublicDefinitions.Add("VK_USE_PLATFORM_IOS_MVK"); + options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/iOS/libMoltenVK.dylib")); + options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/iOS/MoltenVK_icd.json")); + break; default: throw new InvalidPlatformException(options.Platform.Target); } diff --git a/Source/ThirdParty/vorbis/vorbis.Build.cs b/Source/ThirdParty/vorbis/vorbis.Build.cs index 394c8c218..b51d98d23 100644 --- a/Source/ThirdParty/vorbis/vorbis.Build.cs +++ b/Source/ThirdParty/vorbis/vorbis.Build.cs @@ -42,6 +42,7 @@ public class vorbis : DepsModule case TargetPlatform.Android: case TargetPlatform.Switch: case TargetPlatform.Mac: + case TargetPlatform.iOS: options.OutputFiles.Add(Path.Combine(depsRoot, "libvorbis.a")); options.OutputFiles.Add(Path.Combine(depsRoot, "libvorbisenc.a")); options.OutputFiles.Add(Path.Combine(depsRoot, "libvorbisfile.a")); diff --git a/Source/Tools/Flax.Build/Build/DepsModule.cs b/Source/Tools/Flax.Build/Build/DepsModule.cs index a6b32045f..cf71ce2fe 100644 --- a/Source/Tools/Flax.Build/Build/DepsModule.cs +++ b/Source/Tools/Flax.Build/Build/DepsModule.cs @@ -34,6 +34,7 @@ namespace Flax.Build case TargetPlatform.Android: case TargetPlatform.Switch: case TargetPlatform.Mac: + case TargetPlatform.iOS: options.OutputFiles.Add(Path.Combine(path, string.Format("lib{0}.a", name))); break; default: throw new InvalidPlatformException(options.Platform.Target); diff --git a/Source/Tools/Flax.Build/Build/Module.cs b/Source/Tools/Flax.Build/Build/Module.cs index 3fce020de..e00fa1362 100644 --- a/Source/Tools/Flax.Build/Build/Module.cs +++ b/Source/Tools/Flax.Build/Build/Module.cs @@ -103,6 +103,7 @@ namespace Flax.Build case TargetPlatform.Android: return "PLATFORM_ANDROID"; case TargetPlatform.Switch: return "PLATFORM_SWITCH"; case TargetPlatform.Mac: return "PLATFORM_MAC"; + case TargetPlatform.iOS: return "PLATFORM_IOS"; default: throw new InvalidPlatformException(platform); } } diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index fd51d2802..ac9a64b00 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -315,6 +315,7 @@ namespace Flax.Build case TargetPlatform.Android: return targetArchitecture == TargetArchitecture.ARM64; case TargetPlatform.Switch: return targetArchitecture == TargetArchitecture.ARM64; case TargetPlatform.Mac: return targetArchitecture == TargetArchitecture.ARM64 || targetArchitecture == TargetArchitecture.x64; + case TargetPlatform.iOS: return targetArchitecture == TargetArchitecture.ARM64; default: return false; } } diff --git a/Source/Tools/Flax.Build/Build/TargetPlatform.cs b/Source/Tools/Flax.Build/Build/TargetPlatform.cs index 77c4cd042..ceba44887 100644 --- a/Source/Tools/Flax.Build/Build/TargetPlatform.cs +++ b/Source/Tools/Flax.Build/Build/TargetPlatform.cs @@ -56,6 +56,11 @@ namespace Flax.Build /// Running on Mac. /// Mac = 10, + + /// + /// Running on iPhone. + /// + iOS = 11, } /// diff --git a/Source/Tools/Flax.Build/Deploy/Deployer.cs b/Source/Tools/Flax.Build/Deploy/Deployer.cs index b41b34ba4..3daa99a04 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployer.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployer.cs @@ -38,6 +38,7 @@ namespace Flax.Deploy BuildPlatform(TargetPlatform.Windows, TargetArchitecture.x64); BuildPlatform(TargetPlatform.Android, TargetArchitecture.ARM64); BuildPlatform(TargetPlatform.Mac, TargetArchitecture.x64, TargetArchitecture.ARM64); + BuildPlatform(TargetPlatform.iOS, TargetArchitecture.ARM64); } else { diff --git a/Source/Tools/Flax.Build/Globals.cs b/Source/Tools/Flax.Build/Globals.cs index 60a2a2ba5..b5f3088d6 100644 --- a/Source/Tools/Flax.Build/Globals.cs +++ b/Source/Tools/Flax.Build/Globals.cs @@ -36,6 +36,7 @@ namespace Flax.Build TargetPlatform.Android, TargetPlatform.Switch, TargetPlatform.Mac, + TargetPlatform.iOS, }; /// diff --git a/Source/Tools/Flax.Build/Platforms/Apple/ApplePlatform.cs b/Source/Tools/Flax.Build/Platforms/Apple/ApplePlatform.cs new file mode 100644 index 000000000..2059b08de --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/Apple/ApplePlatform.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using Flax.Build.Projects; + +namespace Flax.Build.Platforms +{ + /// + /// The build platform for all Apple systems. + /// + /// + public abstract class ApplePlatform : UnixPlatform + { + /// + public override bool HasRequiredSDKsInstalled { get; } + + /// + public override bool HasSharedLibrarySupport => true; + + /// + public override string SharedLibraryFileExtension => ".dylib"; + + /// + public override string ProgramDatabaseFileExtension => ".dSYM"; + + /// + public override string SharedLibraryFilePrefix => string.Empty; + + /// + public override ProjectFormat DefaultProjectFormat => ProjectFormat.XCode; + + /// + /// Initializes a new instance of the class. + /// + public ApplePlatform() + { + if (Platform.BuildTargetPlatform != TargetPlatform.Mac) + return; + HasRequiredSDKsInstalled = XCode.Instance.IsValid; + } + + public static void FixInstallNameId(string dylibPath) + { + Utilities.Run("install_name_tool", string.Format(" -id \"@rpath/{0}\" \"{1}\"", Path.GetFileName(dylibPath), dylibPath), null, null, Utilities.RunOptions.ThrowExceptionOnError); + } + } +} diff --git a/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs b/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs new file mode 100644 index 000000000..c0ff9f2a2 --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs @@ -0,0 +1,410 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using System.Collections.Generic; +using Flax.Build.Graph; +using Flax.Build.NativeCpp; + +namespace Flax.Build.Platforms +{ + /// + /// The build toolchain for all Apple systems. + /// + /// + public abstract class AppleToolchain : UnixToolchain + { + public string ToolchainPath; + public string SdkPath; + public string LinkerPath; + public string ArchiverPath; + + /// + /// Initializes a new instance of the class. + /// + /// The platform. + /// The target architecture. + /// The XCode SDK prefix to use for the target platform.. + public AppleToolchain(ApplePlatform platform, TargetArchitecture architecture, string sdkPrefix) + : base(platform, architecture) + { + // Setup tools paths + var xCodePath = XCode.Instance.RootPath; + if (xCodePath.Contains("/Xcode")) + { + // XCode App + ToolchainPath = Path.Combine(xCodePath, "Toolchains/XcodeDefault.xctoolchain"); + SdkPath = Path.Combine(xCodePath, $"Platforms/{sdkPrefix}.platform/Developer"); + } + else + { + // XCode Command Line Tools + ToolchainPath = SdkPath = xCodePath; + if (!Directory.Exists(Path.Combine(ToolchainPath, "usr/bin"))) + throw new Exception("Missing XCode Command Line Tools. Run 'xcode-select --install'."); + } + ClangPath = Path.Combine(ToolchainPath, "usr/bin/clang++"); + LinkerPath = Path.Combine(ToolchainPath, "usr/bin/clang++"); + ArchiverPath = Path.Combine(ToolchainPath, "usr/bin/libtool"); + ClangVersion = GetClangVersion(platform.Target, ClangPath); + SdkPath = Path.Combine(SdkPath, "SDKs"); + var sdks = Directory.GetDirectories(SdkPath); + var bestSdk = string.Empty; + var bestSdkVer = new Version(0, 0, 0, 1); + foreach (var sdk in sdks) + { + var name = Path.GetFileName(sdk); + if (!name.StartsWith(sdkPrefix)) + continue; + var versionName = name.Replace(sdkPrefix, "").Replace(".sdk", ""); + if (string.IsNullOrEmpty(versionName)) + continue; + if (!versionName.Contains(".")) + versionName += ".0"; + var version = new Version(versionName); + if (version > bestSdkVer) + { + bestSdkVer = version; + bestSdk = sdk; + } + } + if (bestSdk.Length == 0) + throw new Exception("Failed to find any valid SDK for " + sdkPrefix); + SdkPath = bestSdk; + + // Setup system paths + //SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/include")); + //SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/include/c++/v1")); + //SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/lib/clang", ClangVersion.ToString(3), "include")); + //SystemIncludePaths.Add(Path.Combine(SdkPath, "usr/include")); + SystemLibraryPaths.Add(Path.Combine(SdkPath, "usr/lib")); + } + + /// + public override void LogInfo() + { + Log.Info("Toolchain: " + ToolchainPath); + Log.Info("SDK: " + SdkPath); + Log.Info("Clang version: " + Utilities.ReadProcessOutput(ClangPath, "--version")); + } + + /// + public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List sourceFiles, string outputPath) + { + var compileEnvironment = options.CompileEnv; + var output = new CompileOutput(); + + // Setup arguments shared by all source files + var commonArgs = new List(); + { + commonArgs.Add("-c"); + commonArgs.Add("-fmessage-length=0"); + commonArgs.Add("-pipe"); + commonArgs.Add("-x"); + commonArgs.Add("objective-c++"); + commonArgs.Add("-stdlib=libc++"); + AddArgsCommon(options, commonArgs); + + switch (compileEnvironment.CppVersion) + { + case CppVersion.Cpp14: + commonArgs.Add("-std=c++14"); + break; + case CppVersion.Cpp17: + case CppVersion.Latest: + commonArgs.Add("-std=c++17"); + break; + case CppVersion.Cpp20: + commonArgs.Add("-std=c++20"); + break; + } + + switch (Architecture) + { + case TargetArchitecture.x64: + commonArgs.Add("-msse2"); + break; + } + + commonArgs.Add("-Wdelete-non-virtual-dtor"); + commonArgs.Add("-fno-math-errno"); + commonArgs.Add("-fasm-blocks"); + commonArgs.Add("-fpascal-strings"); + commonArgs.Add("-fdiagnostics-format=msvc"); + + commonArgs.Add("-Wno-absolute-value"); + commonArgs.Add("-Wno-nullability-completeness"); + commonArgs.Add("-Wno-undef-prefix"); + commonArgs.Add("-Wno-expansion-to-defined"); + + // Hide all symbols by default + commonArgs.Add("-fvisibility-inlines-hidden"); + commonArgs.Add("-fvisibility-ms-compat"); + + if (compileEnvironment.RuntimeTypeInfo) + commonArgs.Add("-frtti"); + else + commonArgs.Add("-fno-rtti"); + + if (compileEnvironment.TreatWarningsAsErrors) + commonArgs.Add("-Wall -Werror"); + + // TODO: compileEnvironment.IntrinsicFunctions + // TODO: compileEnvironment.FunctionLevelLinking + // TODO: compileEnvironment.FavorSizeOrSpeed + // TODO: compileEnvironment.RuntimeChecks + // TODO: compileEnvironment.StringPooling + // TODO: compileEnvironment.BufferSecurityCheck + + if (compileEnvironment.DebugInformation) + commonArgs.Add("-gdwarf-2"); + + commonArgs.Add("-pthread"); + + if (compileEnvironment.Optimization) + commonArgs.Add("-O3"); + else + commonArgs.Add("-O0"); + + if (!compileEnvironment.Inlining) + { + commonArgs.Add("-fno-inline-functions"); + commonArgs.Add("-fno-inline"); + } + + if (compileEnvironment.EnableExceptions) + commonArgs.Add("-fexceptions"); + else + commonArgs.Add("-fno-exceptions"); + } + + // Add preprocessor definitions + foreach (var definition in compileEnvironment.PreprocessorDefinitions) + { + commonArgs.Add(string.Format("-D \"{0}\"", definition)); + } + + // Add include paths + foreach (var includePath in compileEnvironment.IncludePaths) + { + commonArgs.Add(string.Format("-I\"{0}\"", includePath.Replace('\\', '/'))); + } + + // Compile all C++ files + var args = new List(); + foreach (var sourceFile in sourceFiles) + { + var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile); + var task = graph.Add(); + + // Use shared arguments + args.Clear(); + args.AddRange(commonArgs); + + // Object File Name + var objFile = Path.Combine(outputPath, sourceFilename + ".o"); + args.Add(string.Format("-o \"{0}\"", objFile.Replace('\\', '/'))); + output.ObjectFiles.Add(objFile); + task.ProducedFiles.Add(objFile); + + // Source File Name + args.Add("\"" + sourceFile.Replace('\\', '/') + "\""); + + // Request included files to exist + var includes = IncludesCache.FindAllIncludedFiles(sourceFile); + task.PrerequisiteFiles.AddRange(includes); + + // Compile + task.WorkingDirectory = options.WorkingDirectory; + task.CommandPath = ClangPath; + task.CommandArguments = string.Join(" ", args); + task.PrerequisiteFiles.Add(sourceFile); + task.InfoMessage = Path.GetFileName(sourceFile); + task.Cost = task.PrerequisiteFiles.Count; // TODO: include source file size estimation to improve tasks sorting + } + + return output; + } + + /// + public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) + { + var linkEnvironment = options.LinkEnv; + var task = graph.Add(); + var isArchive = linkEnvironment.Output == LinkerOutput.StaticLibrary || linkEnvironment.Output == LinkerOutput.ImportLibrary; + + // Setup arguments + var args = new List(); + { + args.Add(string.Format("-o \"{0}\"", outputFilePath)); + AddArgsCommon(options, args); + + if (isArchive) + { + args.Add("-static"); + } + else + { + args.Add("-dead_strip"); + args.Add("-rpath @executable_path/"); + if (linkEnvironment.Output == LinkerOutput.SharedLibrary) + args.Add("-dynamiclib"); + } + } + + // Input libraries + var libraryPaths = new HashSet(); + var dylibs = new HashSet(); + foreach (var library in linkEnvironment.InputLibraries) + { + var dir = Path.GetDirectoryName(library); + var ext = Path.GetExtension(library); + if (ext == ".framework") + { + args.Add(string.Format("-framework {0}", library.Substring(0, library.Length - ext.Length))); + } + else if (string.IsNullOrEmpty(dir)) + { + args.Add(string.Format("\"-l{0}\"", library)); + } + else if (string.IsNullOrEmpty(ext)) + { + // Skip executable + } + else if (ext == ".dylib") + { + // Link against dynamic library + dylibs.Add(library); + task.PrerequisiteFiles.Add(library); + libraryPaths.Add(dir); + args.Add(string.Format("\"{0}\"", library)); + } + else + { + task.PrerequisiteFiles.Add(library); + args.Add(string.Format("\"{0}\"", GetLibName(library))); + } + } + foreach (var library in options.Libraries) + { + var dir = Path.GetDirectoryName(library); + var ext = Path.GetExtension(library); + if (ext == ".framework") + { + args.Add(string.Format("-framework {0}", library.Substring(0, library.Length - ext.Length))); + } + else if (string.IsNullOrEmpty(dir)) + { + args.Add(string.Format("\"-l{0}\"", library)); + } + else if (string.IsNullOrEmpty(ext)) + { + // Skip executable + } + else if (ext == ".dylib") + { + // Link against dynamic library + dylibs.Add(library); + task.PrerequisiteFiles.Add(library); + libraryPaths.Add(dir); + args.Add(string.Format("\"{0}\"", library)); + } + else + { + task.PrerequisiteFiles.Add(library); + args.Add(string.Format("\"{0}\"", GetLibName(library))); + } + } + + // Input files + task.PrerequisiteFiles.AddRange(linkEnvironment.InputFiles); + foreach (var file in linkEnvironment.InputFiles) + { + var ext = Path.GetExtension(file); + if (ext == ".framework") + { + args.Add(string.Format("-framework {0}", file.Substring(0, file.Length - ext.Length))); + } + else + { + args.Add(string.Format("\"{0}\"", file.Replace('\\', '/'))); + } + } + + // Additional lib paths + libraryPaths.AddRange(linkEnvironment.LibraryPaths); + foreach (var path in libraryPaths) + { + args.Add(string.Format("-L\"{0}\"", path.Replace('\\', '/'))); + } + + // Use a response file (it can contain any commands that you would specify on the command line) + bool useResponseFile = true; + string responseFile = null; + if (useResponseFile) + { + responseFile = Path.Combine(options.IntermediateFolder, Path.GetFileName(outputFilePath) + ".response"); + task.PrerequisiteFiles.Add(responseFile); + Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args)); + } + + // Link + task.WorkingDirectory = options.WorkingDirectory; + task.CommandPath = isArchive ? ArchiverPath : LinkerPath; + task.CommandArguments = useResponseFile ? string.Format("@\"{0}\"", responseFile) : string.Join(" ", args); + task.InfoMessage = "Linking " + outputFilePath; + task.Cost = task.PrerequisiteFiles.Count; + task.ProducedFiles.Add(outputFilePath); + + Task lastTask = task; + if (options.LinkEnv.Output == LinkerOutput.Executable) + { + // Fix rpath for dynamic libraries + foreach (var library in dylibs) + { + var rpathTask = graph.Add(); + rpathTask.ProducedFiles.Add(outputFilePath); + rpathTask.WorkingDirectory = options.WorkingDirectory; + rpathTask.CommandPath = "install_name_tool"; + var filename = Path.GetFileName(library); + var outputFolder = Path.GetDirectoryName(outputFilePath); + rpathTask.CommandArguments = string.Format("-change \"{0}/{1}\" \"@loader_path/{1}\" {2}", outputFolder, filename, outputFilePath); + rpathTask.InfoMessage = "Fixing rpath to " + filename; + rpathTask.Cost = 1; + rpathTask.DisableCache = true; + rpathTask.DependentTasks = new HashSet(); + rpathTask.DependentTasks.Add(lastTask); + lastTask = rpathTask; + } + } + if (!options.LinkEnv.DebugInformation) + { + // Strip debug symbols + var stripTask = graph.Add(); + stripTask.ProducedFiles.Add(outputFilePath); + stripTask.WorkingDirectory = options.WorkingDirectory; + stripTask.CommandPath = "strip"; + stripTask.CommandArguments = string.Format("\"{0}\" -S", outputFilePath); + stripTask.InfoMessage = "Striping " + outputFilePath; + stripTask.Cost = 1; + stripTask.DisableCache = true; + stripTask.DependentTasks = new HashSet(); + stripTask.DependentTasks.Add(lastTask); + } + } + + protected virtual void AddArgsCommon(BuildOptions options, List args) + { + args.Add("-isysroot \"" + SdkPath + "\""); + switch (Architecture) + { + case TargetArchitecture.x64: + args.Add("-arch x86_64"); + break; + case TargetArchitecture.ARM64: + args.Add("-arch arm64"); + break; + } + } + } +} diff --git a/Source/Tools/Flax.Build/Platforms/Apple/XCode.cs b/Source/Tools/Flax.Build/Platforms/Apple/XCode.cs new file mode 100644 index 000000000..9405ffcf9 --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/Apple/XCode.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.IO; +using System.Linq; + +namespace Flax.Build.Platforms +{ + /// + /// The XCode app. + /// + /// + public sealed class XCode : Sdk + { + /// + /// The singleton instance. + /// + public static readonly XCode Instance = new XCode(); + + /// + public override TargetPlatform[] Platforms => new[] + { + TargetPlatform.Mac, + }; + + /// + /// Initializes a new instance of the class. + /// + public XCode() + { + if (!Platforms.Contains(Platform.BuildTargetPlatform)) + return; + try + { + RootPath = Utilities.ReadProcessOutput("xcode-select", "--print-path"); + if (string.IsNullOrEmpty(RootPath) || !Directory.Exists(RootPath)) + return; + IsValid = true; + Version = new Version(1, 0); + Log.Verbose(string.Format("Found XCode at {0}", RootPath)); + } + catch + { + } + } + } +} diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 7b740f47f..53d299515 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -1,43 +1,16 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; -using System.IO; -using Flax.Build.Projects; - namespace Flax.Build.Platforms { /// /// The build platform for all Mac systems. /// /// - public sealed class MacPlatform : UnixPlatform + public sealed class MacPlatform : ApplePlatform { /// public override TargetPlatform Target => TargetPlatform.Mac; - /// - public override bool HasRequiredSDKsInstalled { get; } - - /// - public override bool HasSharedLibrarySupport => true; - - /// - public override string SharedLibraryFileExtension => ".dylib"; - - /// - public override string ProgramDatabaseFileExtension => ".dSYM"; - - /// - public override string SharedLibraryFilePrefix => string.Empty; - - /// - public override ProjectFormat DefaultProjectFormat => ProjectFormat.XCode; - - /// - /// XCode Developer path returned by xcode-select. - /// - public string XCodePath; - /// /// Initializes a new instance of the class. /// @@ -45,18 +18,10 @@ namespace Flax.Build.Platforms { if (Platform.BuildTargetPlatform != TargetPlatform.Mac) return; - try - { - // Check if XCode is installed - XCodePath = Utilities.ReadProcessOutput("xcode-select", "--print-path"); - if (string.IsNullOrEmpty(XCodePath) || !Directory.Exists(XCodePath)) - throw new Exception(XCodePath); - Log.Verbose(string.Format("Found XCode at {0}", XCodePath)); - HasRequiredSDKsInstalled = true; - } - catch + if (!HasRequiredSDKsInstalled) { Log.Warning("Missing XCode. Cannot build for Mac platform."); + return; } } @@ -71,14 +36,10 @@ namespace Flax.Build.Platforms { switch (platform) { + case TargetPlatform.iOS: case TargetPlatform.Mac: return HasRequiredSDKsInstalled; default: return false; } } - - public static void FixInstallNameId(string dylibPath) - { - Utilities.Run("install_name_tool", string.Format(" -id \"@rpath/{0}\" \"{1}\"", Path.GetFileName(dylibPath), dylibPath), null, null, Utilities.RunOptions.ThrowExceptionOnError); - } } } diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs index 01acd699c..23fdc66f7 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs @@ -1,9 +1,6 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; -using System.IO; using System.Collections.Generic; -using Flax.Build.Graph; using Flax.Build.NativeCpp; namespace Flax.Build @@ -24,79 +21,16 @@ namespace Flax.Build.Platforms /// The build toolchain for all Mac systems. /// /// - public sealed class MacToolchain : UnixToolchain + public sealed class MacToolchain : AppleToolchain { - public string ToolchainPath; - public string SdkPath; - public string LinkerPath; - public string ArchiverPath; - /// /// Initializes a new instance of the class. /// /// The platform. /// The target architecture. public MacToolchain(MacPlatform platform, TargetArchitecture architecture) - : base(platform, architecture) + : base(platform, architecture, "MacOSX") { - // Setup tools paths - if (platform.XCodePath.Contains("/Xcode")) - { - // XCode App - ToolchainPath = Path.Combine(platform.XCodePath, "Toolchains/XcodeDefault.xctoolchain"); - SdkPath = Path.Combine(platform.XCodePath, "Platforms/MacOSX.platform/Developer"); - } - else - { - // XCode Command Line Tools - ToolchainPath = SdkPath = platform.XCodePath; - if (!Directory.Exists(Path.Combine(ToolchainPath, "usr/bin"))) - throw new Exception("Missing XCode Command Line Tools. Run 'xcode-select --install'."); - } - ClangPath = Path.Combine(ToolchainPath, "usr/bin/clang++"); - LinkerPath = Path.Combine(ToolchainPath, "usr/bin/clang++"); - ArchiverPath = Path.Combine(ToolchainPath, "usr/bin/libtool"); - ClangVersion = GetClangVersion(platform.Target, ClangPath); - SdkPath = Path.Combine(SdkPath, "SDKs"); - var sdks = Directory.GetDirectories(SdkPath); - var sdkPrefix = "MacOSX"; - var bestSdk = string.Empty; - var bestSdkVer = new Version(0, 0, 0, 1); - foreach (var sdk in sdks) - { - var name = Path.GetFileName(sdk); - if (!name.StartsWith(sdkPrefix)) - continue; - var versionName = name.Replace(sdkPrefix, "").Replace(".sdk", ""); - if (string.IsNullOrEmpty(versionName)) - continue; - if (!versionName.Contains(".")) - versionName += ".0"; - var version = new Version(versionName); - if (version > bestSdkVer) - { - bestSdkVer = version; - bestSdk = sdk; - } - } - if (bestSdk.Length == 0) - throw new Exception("Failed to find any valid SDK for " + sdkPrefix); - SdkPath = bestSdk; - - // Setup system paths - //SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/include")); - //SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/include/c++/v1")); - //SystemIncludePaths.Add(Path.Combine(ToolchainPath, "usr/lib/clang", ClangVersion.ToString(3), "include")); - //SystemIncludePaths.Add(Path.Combine(SdkPath, "usr/include")); - SystemLibraryPaths.Add(Path.Combine(SdkPath, "usr/lib")); - } - - /// - public override void LogInfo() - { - Log.Info("Toolchain: " + ToolchainPath); - Log.Info("SDK: " + SdkPath); - Log.Info("Clang version: " + Utilities.ReadProcessOutput(ClangPath, "--version")); } /// @@ -117,324 +51,11 @@ namespace Flax.Build.Platforms options.LinkEnv.InputLibraries.Add("QuartzCore.framework"); } - /// - public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List sourceFiles, string outputPath) + protected override void AddArgsCommon(BuildOptions options, List args) { - var compileEnvironment = options.CompileEnv; - var output = new CompileOutput(); + base.AddArgsCommon(options, args); - // Setup arguments shared by all source files - var commonArgs = new List(); - { - commonArgs.Add("-c"); - commonArgs.Add("-fmessage-length=0"); - commonArgs.Add("-pipe"); - commonArgs.Add("-x"); - commonArgs.Add("objective-c++"); - commonArgs.Add("-stdlib=libc++"); - AddArgsCommon(options, commonArgs); - - switch (compileEnvironment.CppVersion) - { - case CppVersion.Cpp14: - commonArgs.Add("-std=c++14"); - break; - case CppVersion.Cpp17: - case CppVersion.Latest: - commonArgs.Add("-std=c++17"); - break; - case CppVersion.Cpp20: - commonArgs.Add("-std=c++20"); - break; - } - - switch (Architecture) - { - case TargetArchitecture.x64: - commonArgs.Add("-msse2"); - break; - } - - commonArgs.Add("-Wdelete-non-virtual-dtor"); - commonArgs.Add("-fno-math-errno"); - commonArgs.Add("-fasm-blocks"); - commonArgs.Add("-fpascal-strings"); - commonArgs.Add("-fdiagnostics-format=msvc"); - - commonArgs.Add("-Wno-absolute-value"); - commonArgs.Add("-Wno-nullability-completeness"); - commonArgs.Add("-Wno-undef-prefix"); - commonArgs.Add("-Wno-expansion-to-defined"); - - // Hide all symbols by default - commonArgs.Add("-fvisibility-inlines-hidden"); - commonArgs.Add("-fvisibility-ms-compat"); - - if (compileEnvironment.RuntimeTypeInfo) - commonArgs.Add("-frtti"); - else - commonArgs.Add("-fno-rtti"); - - if (compileEnvironment.TreatWarningsAsErrors) - commonArgs.Add("-Wall -Werror"); - - // TODO: compileEnvironment.IntrinsicFunctions - // TODO: compileEnvironment.FunctionLevelLinking - // TODO: compileEnvironment.FavorSizeOrSpeed - // TODO: compileEnvironment.RuntimeChecks - // TODO: compileEnvironment.StringPooling - // TODO: compileEnvironment.BufferSecurityCheck - - if (compileEnvironment.DebugInformation) - commonArgs.Add("-gdwarf-2"); - - commonArgs.Add("-pthread"); - - if (compileEnvironment.Optimization) - commonArgs.Add("-O3"); - else - commonArgs.Add("-O0"); - - if (!compileEnvironment.Inlining) - { - commonArgs.Add("-fno-inline-functions"); - commonArgs.Add("-fno-inline"); - } - - if (compileEnvironment.EnableExceptions) - commonArgs.Add("-fexceptions"); - else - commonArgs.Add("-fno-exceptions"); - } - - // Add preprocessor definitions - foreach (var definition in compileEnvironment.PreprocessorDefinitions) - { - commonArgs.Add(string.Format("-D \"{0}\"", definition)); - } - - // Add include paths - foreach (var includePath in compileEnvironment.IncludePaths) - { - commonArgs.Add(string.Format("-I\"{0}\"", includePath.Replace('\\', '/'))); - } - - // Compile all C++ files - var args = new List(); - foreach (var sourceFile in sourceFiles) - { - var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile); - var task = graph.Add(); - - // Use shared arguments - args.Clear(); - args.AddRange(commonArgs); - - // Object File Name - var objFile = Path.Combine(outputPath, sourceFilename + ".o"); - args.Add(string.Format("-o \"{0}\"", objFile.Replace('\\', '/'))); - output.ObjectFiles.Add(objFile); - task.ProducedFiles.Add(objFile); - - // Source File Name - args.Add("\"" + sourceFile.Replace('\\', '/') + "\""); - - // Request included files to exist - var includes = IncludesCache.FindAllIncludedFiles(sourceFile); - task.PrerequisiteFiles.AddRange(includes); - - // Compile - task.WorkingDirectory = options.WorkingDirectory; - task.CommandPath = ClangPath; - task.CommandArguments = string.Join(" ", args); - task.PrerequisiteFiles.Add(sourceFile); - task.InfoMessage = Path.GetFileName(sourceFile); - task.Cost = task.PrerequisiteFiles.Count; // TODO: include source file size estimation to improve tasks sorting - } - - return output; - } - - /// - public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) - { - var linkEnvironment = options.LinkEnv; - var task = graph.Add(); - var isArchive = linkEnvironment.Output == LinkerOutput.StaticLibrary || linkEnvironment.Output == LinkerOutput.ImportLibrary; - - // Setup arguments - var args = new List(); - { - args.Add(string.Format("-o \"{0}\"", outputFilePath)); - AddArgsCommon(options, args); - - if (isArchive) - { - args.Add("-static"); - } - else - { - args.Add("-dead_strip"); - args.Add("-rpath @executable_path/"); - if (linkEnvironment.Output == LinkerOutput.SharedLibrary) - args.Add("-dynamiclib"); - } - } - - // Input libraries - var libraryPaths = new HashSet(); - var dylibs = new HashSet(); - foreach (var library in linkEnvironment.InputLibraries) - { - var dir = Path.GetDirectoryName(library); - var ext = Path.GetExtension(library); - if (ext == ".framework") - { - args.Add(string.Format("-framework {0}", library.Substring(0, library.Length - ext.Length))); - } - else if (string.IsNullOrEmpty(dir)) - { - args.Add(string.Format("\"-l{0}\"", library)); - } - else if (string.IsNullOrEmpty(ext)) - { - // Skip executable - } - else if (ext == ".dylib") - { - // Link against dynamic library - dylibs.Add(library); - task.PrerequisiteFiles.Add(library); - libraryPaths.Add(dir); - args.Add(string.Format("\"{0}\"", library)); - } - else - { - task.PrerequisiteFiles.Add(library); - args.Add(string.Format("\"{0}\"", GetLibName(library))); - } - } - foreach (var library in options.Libraries) - { - var dir = Path.GetDirectoryName(library); - var ext = Path.GetExtension(library); - if (ext == ".framework") - { - args.Add(string.Format("-framework {0}", library.Substring(0, library.Length - ext.Length))); - } - else if (string.IsNullOrEmpty(dir)) - { - args.Add(string.Format("\"-l{0}\"", library)); - } - else if (string.IsNullOrEmpty(ext)) - { - // Skip executable - } - else if (ext == ".dylib") - { - // Link against dynamic library - dylibs.Add(library); - task.PrerequisiteFiles.Add(library); - libraryPaths.Add(dir); - args.Add(string.Format("\"{0}\"", library)); - } - else - { - task.PrerequisiteFiles.Add(library); - args.Add(string.Format("\"{0}\"", GetLibName(library))); - } - } - - // Input files - task.PrerequisiteFiles.AddRange(linkEnvironment.InputFiles); - foreach (var file in linkEnvironment.InputFiles) - { - var ext = Path.GetExtension(file); - if (ext == ".framework") - { - args.Add(string.Format("-framework {0}", file.Substring(0, file.Length - ext.Length))); - } - else - { - args.Add(string.Format("\"{0}\"", file.Replace('\\', '/'))); - } - } - - // Additional lib paths - libraryPaths.AddRange(linkEnvironment.LibraryPaths); - foreach (var path in libraryPaths) - { - args.Add(string.Format("-L\"{0}\"", path.Replace('\\', '/'))); - } - - // Use a response file (it can contain any commands that you would specify on the command line) - bool useResponseFile = true; - string responseFile = null; - if (useResponseFile) - { - responseFile = Path.Combine(options.IntermediateFolder, Path.GetFileName(outputFilePath) + ".response"); - task.PrerequisiteFiles.Add(responseFile); - Utilities.WriteFileIfChanged(responseFile, string.Join(Environment.NewLine, args)); - } - - // Link - task.WorkingDirectory = options.WorkingDirectory; - task.CommandPath = isArchive ? ArchiverPath : LinkerPath; - task.CommandArguments = useResponseFile ? string.Format("@\"{0}\"", responseFile) : string.Join(" ", args); - task.InfoMessage = "Linking " + outputFilePath; - task.Cost = task.PrerequisiteFiles.Count; - task.ProducedFiles.Add(outputFilePath); - - Task lastTask = task; - if (options.LinkEnv.Output == LinkerOutput.Executable) - { - // Fix rpath for dynamic libraries - foreach (var library in dylibs) - { - var rpathTask = graph.Add(); - rpathTask.ProducedFiles.Add(outputFilePath); - rpathTask.WorkingDirectory = options.WorkingDirectory; - rpathTask.CommandPath = "install_name_tool"; - var filename = Path.GetFileName(library); - var outputFolder = Path.GetDirectoryName(outputFilePath); - rpathTask.CommandArguments = string.Format("-change \"{0}/{1}\" \"@loader_path/{1}\" {2}", outputFolder, filename, outputFilePath); - rpathTask.InfoMessage = "Fixing rpath to " + filename; - rpathTask.Cost = 1; - rpathTask.DisableCache = true; - rpathTask.DependentTasks = new HashSet(); - rpathTask.DependentTasks.Add(lastTask); - lastTask = rpathTask; - } - } - if (!options.LinkEnv.DebugInformation) - { - // Strip debug symbols - var stripTask = graph.Add(); - stripTask.ProducedFiles.Add(outputFilePath); - stripTask.WorkingDirectory = options.WorkingDirectory; - stripTask.CommandPath = "strip"; - stripTask.CommandArguments = string.Format("\"{0}\" -S", outputFilePath); - stripTask.InfoMessage = "Striping " + outputFilePath; - stripTask.Cost = 1; - stripTask.DisableCache = true; - stripTask.DependentTasks = new HashSet(); - stripTask.DependentTasks.Add(lastTask); - } - } - - private void AddArgsCommon(BuildOptions options, List args) - { args.Add("-mmacosx-version-min=" + Configuration.MacOSXMinVer); - args.Add("-isysroot \"" + SdkPath + "\""); - switch (Architecture) - { - case TargetArchitecture.x64: - args.Add("-arch x86_64"); - break; - case TargetArchitecture.ARM64: - args.Add("-arch arm64"); - break; - } } } } diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index 4a1ca8475..68520ef44 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -248,6 +248,12 @@ namespace Flax.Build.Platforms case TargetArchitecture.ARM64: return "aarch64-apple-macos" + Configuration.MacOSXMinVer; default: throw new InvalidArchitectureException(architecture); } + case TargetPlatform.iOS: + switch (architecture) + { + case TargetArchitecture.ARM64: return "aarch64-apple-ios" + Configuration.iOSMinVer; + default: throw new InvalidArchitectureException(architecture); + } default: throw new InvalidPlatformException(platform); } } diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs new file mode 100644 index 000000000..8b82b095b --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +namespace Flax.Build.Platforms +{ + /// + /// The build platform for all iOS systems. + /// + /// + public sealed class iOSPlatform : ApplePlatform + { + /// + public override TargetPlatform Target => TargetPlatform.iOS; + + /// + /// Initializes a new instance of the class. + /// + public iOSPlatform() + { + if (Platform.BuildTargetPlatform != TargetPlatform.Mac) + return; + if (!HasRequiredSDKsInstalled) + { + Log.Warning("Missing XCode. Cannot build for iOS platform."); + return; + } + } + + /// + protected override Toolchain CreateToolchain(TargetArchitecture architecture) + { + return new iOSToolchain(this, architecture); + } + } +} diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs new file mode 100644 index 000000000..9b83d803d --- /dev/null +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using Flax.Build.NativeCpp; + +namespace Flax.Build +{ + partial class Configuration + { + /// + /// Specifies the minimum iOS version to use (eg. 14). + /// + [CommandLine("iOSMinVer", "", "Specifies the minimum iOS version to use (eg. 14).")] + public static string iOSMinVer = "14"; + } +} + +namespace Flax.Build.Platforms +{ + /// + /// The build toolchain for all iOS systems. + /// + /// + public sealed class iOSToolchain : AppleToolchain + { + /// + /// Initializes a new instance of the class. + /// + /// The platform. + /// The target architecture. + public iOSToolchain(iOSPlatform platform, TargetArchitecture architecture) + : base(platform, architecture, "iPhoneOS") + { + } + + /// + public override void SetupEnvironment(BuildOptions options) + { + base.SetupEnvironment(options); + + options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_IOS"); + + // TODO: move this to the specific module configs (eg. Platform.Build.cs) + options.LinkEnv.InputLibraries.Add("z"); + options.LinkEnv.InputLibraries.Add("bz2"); + options.LinkEnv.InputLibraries.Add("CoreFoundation.framework"); + options.LinkEnv.InputLibraries.Add("CoreGraphics.framework"); + options.LinkEnv.InputLibraries.Add("SystemConfiguration.framework"); + options.LinkEnv.InputLibraries.Add("IOKit.framework"); + options.LinkEnv.InputLibraries.Add("UIKit.framework"); + options.LinkEnv.InputLibraries.Add("QuartzCore.framework"); + } + + protected override void AddArgsCommon(BuildOptions options, List args) + { + base.AddArgsCommon(options, args); + + args.Add("-miphoneos-version-min=" + Configuration.iOSMinVer); + } + } +}