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);
+ }
+ }
+}