Add iOS platform (refactor Mac into shared Apple platform impl)
This commit is contained in:
@@ -121,6 +121,12 @@ API_ENUM() enum class BuildPlatform
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="EditorDisplay(null, \"Mac ARM64\")")
|
||||
MacOSARM64 = 13,
|
||||
|
||||
/// <summary>
|
||||
/// iOS (ARM64)
|
||||
/// </summary>
|
||||
API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")")
|
||||
iOSARM64 = 14,
|
||||
};
|
||||
|
||||
extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform);
|
||||
|
||||
@@ -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<MacPlatformTools>(ArchitectureType::ARM64);
|
||||
break;
|
||||
#endif
|
||||
#if PLATFORM_TOOLS_IOS
|
||||
case BuildPlatform::iOSARM64:
|
||||
result = New<iOSPlatformTools>();
|
||||
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: ;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
237
Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
Normal file
237
Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp
Normal file
@@ -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 <ThirdParty/pugixml/pugixml.hpp>
|
||||
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<String> files;
|
||||
FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly);
|
||||
for (auto& file : files)
|
||||
{
|
||||
if (FileSystem::GetExtension(file).IsEmpty())
|
||||
{
|
||||
executableName = StringUtils::GetFileName(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy app icon
|
||||
TextureData iconData;
|
||||
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData))
|
||||
{
|
||||
String 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
|
||||
25
Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h
Normal file
25
Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_TOOLS_IOS
|
||||
|
||||
#include "../../PlatformTools.h"
|
||||
|
||||
/// <summary>
|
||||
/// The iOS platform support tools.
|
||||
/// </summary>
|
||||
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
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#if PLATFORM_LINUX
|
||||
#include <stdio.h>
|
||||
#elif PLATFORM_MAC
|
||||
#include "Engine/Platform/Mac/MacUtils.h"
|
||||
#include "Engine/Platform/Apple/AppleUtils.h"
|
||||
#include <AppKit/AppKit.h>
|
||||
#endif
|
||||
|
||||
@@ -88,7 +88,7 @@ void VisualStudioCodeEditor::FindEditors(Array<CodeEditor*>* 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<VisualStudioCodeEditor>(path, false));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -202,6 +202,14 @@ namespace FlaxEditor.Content.Settings
|
||||
public JsonAsset MacPlatform;
|
||||
#endif
|
||||
|
||||
#if FLAX_EDITOR || PLATFORM_IOS
|
||||
/// <summary>
|
||||
/// Reference to <see cref="iOSPlatformSettings"/> asset. Used to apply configuration on iOS platform.
|
||||
/// </summary>
|
||||
[EditorOrder(2100), EditorDisplay("Platform Settings", "iOS"), AssetReference(typeof(iOSPlatformSettings), true), Tooltip("Reference to iOS Platform Settings asset")]
|
||||
public JsonAsset iOSPlatform;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the game settings asset file.
|
||||
/// </summary>
|
||||
@@ -333,6 +341,10 @@ namespace FlaxEditor.Content.Settings
|
||||
if (type == typeof(MacPlatformSettings))
|
||||
return Load<MacPlatformSettings>(gameSettings.MacPlatform) as T;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_IOS
|
||||
if (type == typeof(iOSPlatformSettings))
|
||||
return Load<iOSPlatformSettings>(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;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
Guid SwitchPlatform;
|
||||
Guid PS5Platform;
|
||||
Guid MacPlatform;
|
||||
Guid iOSPlatform;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -35,3 +35,6 @@
|
||||
#if PLATFORM_MAC
|
||||
#include "Engine/Platform/Mac/MacPlatformSettings.h"
|
||||
#endif
|
||||
#if PLATFORM_IOS
|
||||
#include "Engine/Platform/iOS/iOSPlatformSettings.h"
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
19
Source/Engine/Engine/iOS/iOSGame.h
Normal file
19
Source/Engine/Engine/iOS/iOSGame.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_IOS
|
||||
|
||||
#include "../Base/GameBase.h"
|
||||
|
||||
/// <summary>
|
||||
/// The game class implementation for iOS platform.
|
||||
/// </summary>
|
||||
/// <seealso cref="Game" />
|
||||
class iOSGame : public GameBase
|
||||
{
|
||||
};
|
||||
|
||||
typedef iOSGame Game;
|
||||
|
||||
#endif
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <UIKit/UIKit.h>
|
||||
|
||||
void iOSVulkanPlatform::GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& 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
|
||||
26
Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
Normal file
26
Source/Engine/GraphicsDevice/Vulkan/iOS/iOSVulkanPlatform.h
Normal file
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// The implementation for the Vulkan API support for iOS platform.
|
||||
/// </summary>
|
||||
class iOSVulkanPlatform : public VulkanPlatformBase
|
||||
{
|
||||
public:
|
||||
static void GetInstanceExtensions(Array<const char*>& extensions, Array<const char*>& layers);
|
||||
static void CreateSurface(void* windowHandle, VkInstance instance, VkSurfaceKHR* outSurface);
|
||||
};
|
||||
|
||||
typedef iOSVulkanPlatform VulkanPlatform;
|
||||
|
||||
#endif
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
532
Source/Engine/Platform/Apple/AppleFileSystem.cpp
Normal file
532
Source/Engine/Platform/Apple/AppleFileSystem.cpp
Normal file
@@ -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 <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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<String>& 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<String>& 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<String>& 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<String>& 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
|
||||
52
Source/Engine/Platform/Apple/AppleFileSystem.h
Normal file
52
Source/Engine/Platform/Apple/AppleFileSystem.h
Normal file
@@ -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"
|
||||
|
||||
/// <summary>
|
||||
/// Apple platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
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<String, HeapAllocation>& results, const String& path, const Char* searchPattern, DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& 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:
|
||||
|
||||
/// <summary>
|
||||
/// Gets last time when file has been modified (in UTC).
|
||||
/// </summary>
|
||||
/// <param name="path">The file path to check.</param>
|
||||
/// <returns>The last write time or DateTime::MinValue() if cannot get data.</returns>
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special folder path.
|
||||
/// </summary>
|
||||
/// <param name="type">The folder type.</param>
|
||||
/// <param name="result">The result full path.</param>
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
418
Source/Engine/Platform/Apple/ApplePlatform.cpp
Normal file
418
Source/Engine/Platform/Apple/ApplePlatform.cpp
Normal file
@@ -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 <unistd.h>
|
||||
#include <cstdint>
|
||||
#include <stdlib.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <uuid/uuid.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <SystemConfiguration/SystemConfiguration.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <dlfcn.h>
|
||||
#if CRASH_LOG_ENABLE
|
||||
#include <execinfo.h>
|
||||
#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<User>(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::StackFrame> ApplePlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context)
|
||||
{
|
||||
Array<StackFrame> result;
|
||||
#if CRASH_LOG_ENABLE
|
||||
void* callstack[120];
|
||||
skipCount = Math::Min<int32>(skipCount, ARRAY_COUNT(callstack));
|
||||
int32 maxCount = Math::Min<int32>(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<int32>(StringUtils::Length(name), ARRAY_COUNT(frame.FunctionName) - 1);
|
||||
Platform::MemoryCopy(frame.FunctionName, name, nameLen);
|
||||
frame.FunctionName[nameLen] = 0;
|
||||
|
||||
}
|
||||
free(names);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
107
Source/Engine/Platform/Apple/ApplePlatform.h
Normal file
107
Source/Engine/Platform/Apple/ApplePlatform.h
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
|
||||
#include "../Unix/UnixPlatform.h"
|
||||
|
||||
/// <summary>
|
||||
/// The Apple platform implementation and application management utilities.
|
||||
/// </summary>
|
||||
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<char const*>(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<StackFrame, HeapAllocation> GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -2,25 +2,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_MAC
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
|
||||
#include "../Unix/UnixThread.h"
|
||||
#include <signal.h>
|
||||
|
||||
/// <summary>
|
||||
/// Thread object for Mac platform.
|
||||
/// Thread object for Apple platform.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API MacThread : public UnixThread
|
||||
class FLAXENGINE_API AppleThread : public UnixThread
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MacThread"/> class.
|
||||
/// Initializes a new instance of the <see cref="AppleThread"/> class.
|
||||
/// </summary>
|
||||
/// <param name="runnable">The runnable.</param>
|
||||
/// <param name="name">The thread name.</param>
|
||||
/// <param name="priority">The thread priority.</param>
|
||||
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:
|
||||
/// <param name="priority">Tells the thread whether it needs to adjust its priority or not. Defaults to normal priority</param>
|
||||
/// <param name="stackSize">The size of the stack to create. 0 means use the current thread's stack size</param>
|
||||
/// <returns>Pointer to the new thread or null if cannot create it</returns>
|
||||
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<MacThread>(runnable, name, priority), stackSize);
|
||||
return (AppleThread*)Setup(New<AppleThread>(runnable, name, priority), stackSize);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -2,15 +2,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_MAC || PLATFORM_IOS
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
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
|
||||
@@ -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("");
|
||||
}
|
||||
|
||||
@@ -665,7 +665,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="screenPos">The screen position (in pixels).</param>
|
||||
/// <returns>The monitor bounds.</returns>
|
||||
API_FUNCTION() static Rectangle GetMonitorBounds(const Float2& screenPos) = delete;
|
||||
API_FUNCTION() static Rectangle GetMonitorBounds(const Float2& screenPos);
|
||||
|
||||
/// <summary>
|
||||
/// Gets size of the primary desktop.
|
||||
@@ -677,7 +677,7 @@ public:
|
||||
/// Gets virtual bounds of the desktop made of all the monitors outputs attached.
|
||||
/// </summary>
|
||||
/// <returns>Whole desktop size.</returns>
|
||||
API_PROPERTY() static Rectangle GetVirtualDesktopBounds() = delete;
|
||||
API_PROPERTY() static Rectangle GetVirtualDesktopBounds();
|
||||
|
||||
/// <summary>
|
||||
/// Gets virtual size of the desktop made of all the monitors outputs attached.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -58,6 +58,11 @@ API_ENUM() enum class PlatformType
|
||||
/// Running on Mac.
|
||||
/// </summary>
|
||||
Mac = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Running on iPhone.
|
||||
/// </summary>
|
||||
iOS = 11,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, String>& result)
|
||||
{
|
||||
const LPWCH environmentStr = GetEnvironmentStringsW();
|
||||
|
||||
@@ -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<String, String, HeapAllocation>& result);
|
||||
static bool GetEnvironmentVariable(const String& name, String& value);
|
||||
static bool SetEnvironmentVariable(const String& name, const String& value);
|
||||
|
||||
@@ -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 <fcntl.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
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<String>& 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<String>& 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<String>& 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<String>& 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
|
||||
|
||||
@@ -4,53 +4,20 @@
|
||||
|
||||
#if PLATFORM_MAC
|
||||
|
||||
#include "Engine/Platform/Base/FileSystemBase.h"
|
||||
#include "../Apple/AppleFileSystem.h"
|
||||
|
||||
/// <summary>
|
||||
/// Mac platform implementation of filesystem service.
|
||||
/// </summary>
|
||||
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<String, HeapAllocation>& filenames);
|
||||
static bool ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& 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<String, HeapAllocation>& results, const String& path, const Char* searchPattern, DirectorySearchOption option = DirectorySearchOption::AllDirectories);
|
||||
static bool GetChildDirectories(Array<String, HeapAllocation>& 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:
|
||||
|
||||
/// <summary>
|
||||
/// Gets last time when file has been modified (in UTC).
|
||||
/// </summary>
|
||||
/// <param name="path">The file path to check.</param>
|
||||
/// <returns>The last write time or DateTime::MinValue() if cannot get data.</returns>
|
||||
static DateTime GetFileLastEditTime(const StringView& path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special folder path.
|
||||
/// </summary>
|
||||
/// <param name="type">The folder type.</param>
|
||||
/// <param name="result">The result full path.</param>
|
||||
static void GetSpecialFolderPath(const SpecialFolder type, String& result);
|
||||
|
||||
private:
|
||||
|
||||
static bool getFilesFromDirectoryTop(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
static bool getFilesFromDirectoryAll(Array<String, HeapAllocation>& results, const char* path, const char* searchPattern);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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 <execinfo.h>
|
||||
#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<byte>& data)
|
||||
{
|
||||
}
|
||||
|
||||
void MacClipboard::SetFiles(const Array<String>& 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<byte> MacClipboard::GetRawData()
|
||||
{
|
||||
return Array<byte>();
|
||||
}
|
||||
|
||||
Array<String> MacClipboard::GetFiles()
|
||||
{
|
||||
return Array<String>();
|
||||
}
|
||||
|
||||
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<byte>& data)
|
||||
{
|
||||
}
|
||||
|
||||
void MacClipboard::SetFiles(const Array<String>& 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<byte> MacClipboard::GetRawData()
|
||||
{
|
||||
return Array<byte>();
|
||||
}
|
||||
|
||||
Array<String> MacClipboard::GetFiles()
|
||||
{
|
||||
return Array<String>();
|
||||
}
|
||||
|
||||
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<User>(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<MacMouse>();
|
||||
Input::Keyboard = New<MacKeyboard>();
|
||||
@@ -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<MacWindow>(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<String, String>& 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::StackFrame> MacPlatform::GetStackFrames(int32 skipCount, int32 maxDepth, void* context)
|
||||
{
|
||||
Array<StackFrame> result;
|
||||
#if CRASH_LOG_ENABLE
|
||||
void* callstack[120];
|
||||
skipCount = Math::Min<int32>(skipCount, ARRAY_COUNT(callstack));
|
||||
int32 maxCount = Math::Min<int32>(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<int32>(StringUtils::Length(name), ARRAY_COUNT(frame.FunctionName) - 1);
|
||||
Platform::MemoryCopy(frame.FunctionName, name, nameLen);
|
||||
frame.FunctionName[nameLen] = 0;
|
||||
|
||||
}
|
||||
free(names);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,115 +4,33 @@
|
||||
|
||||
#if PLATFORM_MAC
|
||||
|
||||
#include "../Unix/UnixPlatform.h"
|
||||
#include "../Apple/ApplePlatform.h"
|
||||
|
||||
/// <summary>
|
||||
/// The Mac platform implementation and application management utilities.
|
||||
/// </summary>
|
||||
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<char const*>(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<String, String, HeapAllocation>& 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<StackFrame, HeapAllocation> GetStackFrames(int32 skipCount = 0, int32 maxDepth = 60, void* context = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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<NSDraggingInfo> 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<NSDraggingInfo> 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
17
Source/Engine/Platform/iOS/iOSDefines.h
Normal file
17
Source/Engine/Platform/iOS/iOSDefines.h
Normal file
@@ -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
|
||||
91
Source/Engine/Platform/iOS/iOSPlatform.cpp
Normal file
91
Source/Engine/Platform/iOS/iOSPlatform.cpp
Normal file
@@ -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 <UIKit/UIKit.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
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<iOSWindow>(settings);
|
||||
}
|
||||
|
||||
#endif
|
||||
28
Source/Engine/Platform/iOS/iOSPlatform.h
Normal file
28
Source/Engine/Platform/iOS/iOSPlatform.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if PLATFORM_IOS
|
||||
|
||||
#include "../Apple/ApplePlatform.h"
|
||||
|
||||
/// <summary>
|
||||
/// The iOS platform implementation and application management utilities.
|
||||
/// </summary>
|
||||
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
|
||||
50
Source/Engine/Platform/iOS/iOSPlatformSettings.h
Normal file
50
Source/Engine/Platform/iOS/iOSPlatformSettings.h
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// iOS platform settings.
|
||||
/// </summary>
|
||||
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API iOSPlatformSettings : public SettingsBase
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(iOSPlatformSettings);
|
||||
|
||||
/// <summary>
|
||||
/// The app identifier (reversed DNS, eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")")
|
||||
String AppIdentifier = TEXT("com.${COMPANY_NAME}.${PROJECT_NAME}");
|
||||
|
||||
/// <summary>
|
||||
/// Custom icon texture to use for the application (overrides the default one).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Other\")")
|
||||
SoftObjectReference<Texture> OverrideIcon;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
|
||||
/// </summary>
|
||||
static iOSPlatformSettings* Get();
|
||||
|
||||
// [SettingsBase]
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
|
||||
{
|
||||
DESERIALIZE(AppIdentifier);
|
||||
DESERIALIZE(OverrideIcon);
|
||||
}
|
||||
};
|
||||
|
||||
#if PLATFORM_IOS
|
||||
typedef iOSPlatformSettings PlatformSettings;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
212
Source/Engine/Platform/iOS/iOSWindow.cpp
Normal file
212
Source/Engine/Platform/iOS/iOSWindow.cpp
Normal file
@@ -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 <UIKit/UIKit.h>
|
||||
#include <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
@interface iOSUIWindow : UIWindow
|
||||
{
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation iOSUIWindow
|
||||
|
||||
@end
|
||||
|
||||
@interface iOSUIViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
@interface iOSUIView : UIView
|
||||
|
||||
@property iOSWindow* window;
|
||||
|
||||
@end
|
||||
|
||||
@implementation iOSUIView
|
||||
|
||||
+(Class) layerClass { return [CAMetalLayer class]; }
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
if (!_window)
|
||||
return;
|
||||
float scale = [[UIScreen mainScreen] scale];
|
||||
_window->CheckForResize((float)frame.size.width * scale, (float)frame.size.height * scale);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation iOSUIViewController
|
||||
{
|
||||
}
|
||||
|
||||
- (BOOL)prefersHomeIndicatorAutoHidden
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation
|
||||
{
|
||||
return UIStatusBarAnimationSlide;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
iOSWindow::iOSWindow(const CreateWindowSettings& settings)
|
||||
: WindowBase(settings)
|
||||
{
|
||||
// Fullscreen by default
|
||||
CGRect frame = [[UIScreen mainScreen] bounds];
|
||||
float scale = [[UIScreen mainScreen] scale];
|
||||
_clientSize = Float2((float)frame.size.width * scale, (float)frame.size.height * scale);
|
||||
|
||||
// Setup view
|
||||
_view = [[iOSUIView alloc] initWithFrame:frame];
|
||||
iOSUIView* v = (iOSUIView*)_view;
|
||||
[v resignFirstResponder];
|
||||
[v setNeedsDisplay];
|
||||
[v setHidden:NO];
|
||||
[v setOpaque:YES];
|
||||
[v 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
|
||||
49
Source/Engine/Platform/iOS/iOSWindow.h
Normal file
49
Source/Engine/Platform/iOS/iOSWindow.h
Normal file
@@ -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"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the window class for iOS platform.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API iOSWindow : public WindowBase
|
||||
{
|
||||
private:
|
||||
|
||||
Float2 _clientSize;
|
||||
void* _window;
|
||||
void* _layer;
|
||||
void* _view;
|
||||
void* _viewController;
|
||||
|
||||
public:
|
||||
|
||||
iOSWindow(const CreateWindowSettings& settings);
|
||||
~iOSWindow();
|
||||
|
||||
void CheckForResize(float width, float height);
|
||||
|
||||
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
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
2
Source/ThirdParty/enet/enet.h
vendored
2
Source/ThirdParty/enet/enet.h
vendored
@@ -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) {
|
||||
|
||||
1
Source/ThirdParty/freetype/freetype.Build.cs
vendored
1
Source/ThirdParty/freetype/freetype.Build.cs
vendored
@@ -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);
|
||||
|
||||
1
Source/ThirdParty/ogg/ogg.Build.cs
vendored
1
Source/ThirdParty/ogg/ogg.Build.cs
vendored
@@ -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);
|
||||
|
||||
5
Source/ThirdParty/volk/volk.Build.cs
vendored
5
Source/ThirdParty/volk/volk.Build.cs
vendored
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
1
Source/ThirdParty/vorbis/vorbis.Build.cs
vendored
1
Source/ThirdParty/vorbis/vorbis.Build.cs
vendored
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ namespace Flax.Build
|
||||
/// Running on Mac.
|
||||
/// </summary>
|
||||
Mac = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Running on iPhone.
|
||||
/// </summary>
|
||||
iOS = 11,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Flax.Build
|
||||
TargetPlatform.Android,
|
||||
TargetPlatform.Switch,
|
||||
TargetPlatform.Mac,
|
||||
TargetPlatform.iOS,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
48
Source/Tools/Flax.Build/Platforms/Apple/ApplePlatform.cs
Normal file
48
Source/Tools/Flax.Build/Platforms/Apple/ApplePlatform.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The build platform for all Apple systems.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnixPlatform" />
|
||||
public abstract class ApplePlatform : UnixPlatform
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool HasRequiredSDKsInstalled { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasSharedLibrarySupport => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SharedLibraryFileExtension => ".dylib";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ProgramDatabaseFileExtension => ".dSYM";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SharedLibraryFilePrefix => string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ProjectFormat DefaultProjectFormat => ProjectFormat.XCode;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Flax.Build.Platforms.ApplePlatform"/> class.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
410
Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs
Normal file
410
Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The build toolchain for all Apple systems.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnixToolchain" />
|
||||
public abstract class AppleToolchain : UnixToolchain
|
||||
{
|
||||
public string ToolchainPath;
|
||||
public string SdkPath;
|
||||
public string LinkerPath;
|
||||
public string ArchiverPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AppleToolchain"/> class.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform.</param>
|
||||
/// <param name="architecture">The target architecture.</param>
|
||||
/// <param name="sdkPrefix">The XCode SDK prefix to use for the target platform..</param>
|
||||
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"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void LogInfo()
|
||||
{
|
||||
Log.Info("Toolchain: " + ToolchainPath);
|
||||
Log.Info("SDK: " + SdkPath);
|
||||
Log.Info("Clang version: " + Utilities.ReadProcessOutput(ClangPath, "--version"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List<string> sourceFiles, string outputPath)
|
||||
{
|
||||
var compileEnvironment = options.CompileEnv;
|
||||
var output = new CompileOutput();
|
||||
|
||||
// Setup arguments shared by all source files
|
||||
var commonArgs = new List<string>();
|
||||
{
|
||||
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<string>();
|
||||
foreach (var sourceFile in sourceFiles)
|
||||
{
|
||||
var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile);
|
||||
var task = graph.Add<CompileCppTask>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath)
|
||||
{
|
||||
var linkEnvironment = options.LinkEnv;
|
||||
var task = graph.Add<LinkTask>();
|
||||
var isArchive = linkEnvironment.Output == LinkerOutput.StaticLibrary || linkEnvironment.Output == LinkerOutput.ImportLibrary;
|
||||
|
||||
// Setup arguments
|
||||
var args = new List<string>();
|
||||
{
|
||||
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<string>();
|
||||
var dylibs = new HashSet<string>();
|
||||
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<Task>();
|
||||
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<Task>();
|
||||
rpathTask.DependentTasks.Add(lastTask);
|
||||
lastTask = rpathTask;
|
||||
}
|
||||
}
|
||||
if (!options.LinkEnv.DebugInformation)
|
||||
{
|
||||
// Strip debug symbols
|
||||
var stripTask = graph.Add<Task>();
|
||||
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<Task>();
|
||||
stripTask.DependentTasks.Add(lastTask);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AddArgsCommon(BuildOptions options, List<string> args)
|
||||
{
|
||||
args.Add("-isysroot \"" + SdkPath + "\"");
|
||||
switch (Architecture)
|
||||
{
|
||||
case TargetArchitecture.x64:
|
||||
args.Add("-arch x86_64");
|
||||
break;
|
||||
case TargetArchitecture.ARM64:
|
||||
args.Add("-arch arm64");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Source/Tools/Flax.Build/Platforms/Apple/XCode.cs
Normal file
47
Source/Tools/Flax.Build/Platforms/Apple/XCode.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The XCode app.
|
||||
/// </summary>
|
||||
/// <seealso cref="Sdk" />
|
||||
public sealed class XCode : Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// The singleton instance.
|
||||
/// </summary>
|
||||
public static readonly XCode Instance = new XCode();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TargetPlatform[] Platforms => new[]
|
||||
{
|
||||
TargetPlatform.Mac,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XCode"/> class.
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The build platform for all Mac systems.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnixPlatform" />
|
||||
public sealed class MacPlatform : UnixPlatform
|
||||
public sealed class MacPlatform : ApplePlatform
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TargetPlatform Target => TargetPlatform.Mac;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasRequiredSDKsInstalled { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool HasSharedLibrarySupport => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SharedLibraryFileExtension => ".dylib";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ProgramDatabaseFileExtension => ".dSYM";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string SharedLibraryFilePrefix => string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ProjectFormat DefaultProjectFormat => ProjectFormat.XCode;
|
||||
|
||||
/// <summary>
|
||||
/// XCode Developer path returned by xcode-select.
|
||||
/// </summary>
|
||||
public string XCodePath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Flax.Build.Platforms.MacPlatform"/> class.
|
||||
/// </summary>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnixToolchain" />
|
||||
public sealed class MacToolchain : UnixToolchain
|
||||
public sealed class MacToolchain : AppleToolchain
|
||||
{
|
||||
public string ToolchainPath;
|
||||
public string SdkPath;
|
||||
public string LinkerPath;
|
||||
public string ArchiverPath;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MacToolchain"/> class.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform.</param>
|
||||
/// <param name="architecture">The target architecture.</param>
|
||||
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"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void LogInfo()
|
||||
{
|
||||
Log.Info("Toolchain: " + ToolchainPath);
|
||||
Log.Info("SDK: " + SdkPath);
|
||||
Log.Info("Clang version: " + Utilities.ReadProcessOutput(ClangPath, "--version"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -117,324 +51,11 @@ namespace Flax.Build.Platforms
|
||||
options.LinkEnv.InputLibraries.Add("QuartzCore.framework");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List<string> sourceFiles, string outputPath)
|
||||
protected override void AddArgsCommon(BuildOptions options, List<string> args)
|
||||
{
|
||||
var compileEnvironment = options.CompileEnv;
|
||||
var output = new CompileOutput();
|
||||
base.AddArgsCommon(options, args);
|
||||
|
||||
// Setup arguments shared by all source files
|
||||
var commonArgs = new List<string>();
|
||||
{
|
||||
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<string>();
|
||||
foreach (var sourceFile in sourceFiles)
|
||||
{
|
||||
var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile);
|
||||
var task = graph.Add<CompileCppTask>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath)
|
||||
{
|
||||
var linkEnvironment = options.LinkEnv;
|
||||
var task = graph.Add<LinkTask>();
|
||||
var isArchive = linkEnvironment.Output == LinkerOutput.StaticLibrary || linkEnvironment.Output == LinkerOutput.ImportLibrary;
|
||||
|
||||
// Setup arguments
|
||||
var args = new List<string>();
|
||||
{
|
||||
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<string>();
|
||||
var dylibs = new HashSet<string>();
|
||||
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<Task>();
|
||||
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<Task>();
|
||||
rpathTask.DependentTasks.Add(lastTask);
|
||||
lastTask = rpathTask;
|
||||
}
|
||||
}
|
||||
if (!options.LinkEnv.DebugInformation)
|
||||
{
|
||||
// Strip debug symbols
|
||||
var stripTask = graph.Add<Task>();
|
||||
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<Task>();
|
||||
stripTask.DependentTasks.Add(lastTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddArgsCommon(BuildOptions options, List<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
34
Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs
Normal file
34
Source/Tools/Flax.Build/Platforms/iOS/iOSPlatform.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace Flax.Build.Platforms
|
||||
{
|
||||
/// <summary>
|
||||
/// The build platform for all iOS systems.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnixPlatform" />
|
||||
public sealed class iOSPlatform : ApplePlatform
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TargetPlatform Target => TargetPlatform.iOS;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Flax.Build.Platforms.iOSPlatform"/> class.
|
||||
/// </summary>
|
||||
public iOSPlatform()
|
||||
{
|
||||
if (Platform.BuildTargetPlatform != TargetPlatform.Mac)
|
||||
return;
|
||||
if (!HasRequiredSDKsInstalled)
|
||||
{
|
||||
Log.Warning("Missing XCode. Cannot build for iOS platform.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Toolchain CreateToolchain(TargetArchitecture architecture)
|
||||
{
|
||||
return new iOSToolchain(this, architecture);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs
Normal file
61
Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the minimum iOS version to use (eg. 14).
|
||||
/// </summary>
|
||||
[CommandLine("iOSMinVer", "<version>", "Specifies the minimum iOS version to use (eg. 14).")]
|
||||
public static string iOSMinVer = "14";
|
||||
}
|
||||
}
|
||||
|
||||
namespace Flax.Build.Platforms
|
||||
{
|
||||
/// <summary>
|
||||
/// The build toolchain for all iOS systems.
|
||||
/// </summary>
|
||||
/// <seealso cref="UnixToolchain" />
|
||||
public sealed class iOSToolchain : AppleToolchain
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="iOSToolchain"/> class.
|
||||
/// </summary>
|
||||
/// <param name="platform">The platform.</param>
|
||||
/// <param name="architecture">The target architecture.</param>
|
||||
public iOSToolchain(iOSPlatform platform, TargetArchitecture architecture)
|
||||
: base(platform, architecture, "iPhoneOS")
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<string> args)
|
||||
{
|
||||
base.AddArgsCommon(options, args);
|
||||
|
||||
args.Add("-miphoneos-version-min=" + Configuration.iOSMinVer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user