diff --git a/Source/Editor/Content/Create/SettingsCreateEntry.cs b/Source/Editor/Content/Create/SettingsCreateEntry.cs
index 73b89ac8f..fa359fa32 100644
--- a/Source/Editor/Content/Create/SettingsCreateEntry.cs
+++ b/Source/Editor/Content/Create/SettingsCreateEntry.cs
@@ -42,6 +42,7 @@ namespace FlaxEditor.Content.Create
SwitchPlatformSettings,
[EditorDisplay(null, "PS5 Platform Settings")]
PS5PlatformSettings,
+ MacPlatformSettings,
}
private static readonly Type[] _types =
@@ -66,6 +67,7 @@ namespace FlaxEditor.Content.Create
typeof(AndroidPlatformSettings),
TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename),
TypeUtils.GetManagedType(GameSettings.PS5PlatformSettingsTypename),
+ typeof(MacPlatformSettings),
};
internal class Options
@@ -236,6 +238,11 @@ namespace FlaxEditor.Content.Create
return false;
instance.PS5Platform = asset;
break;
+ case SettingsTypes.MacPlatformSettings:
+ if (instance.MacPlatform != null)
+ return false;
+ instance.MacPlatform = asset;
+ break;
}
return true;
}
diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h
index 0d8236e83..8c1113f71 100644
--- a/Source/Editor/Cooker/CookingData.h
+++ b/Source/Editor/Cooker/CookingData.h
@@ -99,6 +99,12 @@ API_ENUM() enum class BuildPlatform
///
API_ENUM(Attributes="EditorDisplay(null, \"PlayStation 5\")")
PS5 = 11,
+
+ ///
+ /// MacOS (x86-64 Intel)
+ ///
+ API_ENUM(Attributes="EditorDisplay(null, \"Mac x64\")")
+ MacOSx64 = 12,
};
extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform);
diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp
index 452f4d230..38f94abd2 100644
--- a/Source/Editor/Cooker/GameCooker.cpp
+++ b/Source/Editor/Cooker/GameCooker.cpp
@@ -58,6 +58,10 @@
#if PLATFORM_TOOLS_SWITCH
#include "Platforms/Switch/Editor/PlatformTools/SwitchPlatformTools.h"
#endif
+#if PLATFORM_TOOLS_MAC
+#include "Platform/Mac/MacPlatformTools.h"
+#include "Engine/Platform/Mac/MacPlatformSettings.h"
+#endif
namespace GameCookerImpl
{
@@ -129,6 +133,8 @@ const Char* ToString(const BuildPlatform platform)
return TEXT("Switch");
case BuildPlatform::PS5:
return TEXT("PlayStation 5");
+ case BuildPlatform::MacOSx64:
+ return TEXT("Mac x64");
default:
return TEXT("?");
}
@@ -325,6 +331,11 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::PS5:
result = New();
break;
+#endif
+#if PLATFORM_TOOLS_MAC
+ case BuildPlatform::MacOSx64:
+ result = New(ArchitectureType::x64);
+ break;
#endif
}
Tools.Add(platform, result);
diff --git a/Source/Editor/Cooker/GameCooker.cs b/Source/Editor/Cooker/GameCooker.cs
index 0d83dff24..d6b1daed7 100644
--- a/Source/Editor/Cooker/GameCooker.cs
+++ b/Source/Editor/Cooker/GameCooker.cs
@@ -103,6 +103,7 @@ namespace FlaxEditor
case BuildPlatform.AndroidARM64: return PlatformType.Android;
case BuildPlatform.XboxScarlett: return PlatformType.XboxScarlett;
case BuildPlatform.Switch: return PlatformType.Switch;
+ case BuildPlatform.MacOSx64: return PlatformType.Mac;
default: throw new ArgumentOutOfRangeException(nameof(buildPlatform), buildPlatform, null);
}
}
diff --git a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
index ee7f87bd6..e00c8cd78 100644
--- a/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
+++ b/Source/Editor/Cooker/Steps/CompileScriptsStep.cpp
@@ -186,6 +186,10 @@ bool CompileScriptsStep::Perform(CookingData& data)
platform = TEXT("PS5");
architecture = TEXT("x64");
break;
+ case BuildPlatform::MacOSx64:
+ platform = TEXT("Mac");
+ architecture = TEXT("x64");
+ break;
default:
LOG(Error, "Unknown or unsupported build platform.");
return true;
diff --git a/Source/Editor/Editor.Build.cs b/Source/Editor/Editor.Build.cs
index c51e72fa1..9d18158d0 100644
--- a/Source/Editor/Editor.Build.cs
+++ b/Source/Editor/Editor.Build.cs
@@ -61,8 +61,16 @@ public class Editor : EditorModule
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT", "PLATFORM_TOOLS_GDK");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH");
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");
+ }
+ else if (options.Platform.Target == TargetPlatform.Linux)
+ {
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");
+ }
+ else if (options.Platform.Target == TargetPlatform.Mac)
+ {
+ AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Mac", "PLATFORM_TOOLS_MAC");
}
- AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");
// Visual Studio integration
if (options.Platform.Target == TargetPlatform.Windows && Flax.Build.Platform.BuildTargetPlatform == TargetPlatform.Windows)
diff --git a/Source/Editor/GUI/PlatformSelector.cs b/Source/Editor/GUI/PlatformSelector.cs
index bb02bb95c..a2089df57 100644
--- a/Source/Editor/GUI/PlatformSelector.cs
+++ b/Source/Editor/GUI/PlatformSelector.cs
@@ -91,6 +91,7 @@ namespace FlaxEditor.GUI
new PlatformData(PlatformType.Android, icons.AndroidIcon128, "Android"),
new PlatformData(PlatformType.Switch, icons.SwitchIcon128, "Switch"),
new PlatformData(PlatformType.PS5, icons.PS5Icon128, "PlayStation 5"),
+ new PlatformData(PlatformType.Mac, icons.Flax64, "Mac"),
};
const float IconSize = 64.0f;
diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs
index a4fb00876..b94f5df25 100644
--- a/Source/Editor/Modules/ContentDatabaseModule.cs
+++ b/Source/Editor/Modules/ContentDatabaseModule.cs
@@ -945,6 +945,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new SettingsProxy(typeof(UWPPlatformSettings), Editor.Instance.Icons.UWPSettings128));
Proxy.Add(new SettingsProxy(typeof(LinuxPlatformSettings), Editor.Instance.Icons.LinuxSettings128));
Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings), Editor.Instance.Icons.AndroidSettings128));
+ Proxy.Add(new SettingsProxy(typeof(MacPlatformSettings), Editor.Instance.Icons.Document128));
var typePS4PlatformSettings = TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename);
if (typePS4PlatformSettings != null)
diff --git a/Source/Editor/Surface/Archetypes/Tools.cs b/Source/Editor/Surface/Archetypes/Tools.cs
index 596a7999c..3a5f87948 100644
--- a/Source/Editor/Surface/Archetypes/Tools.cs
+++ b/Source/Editor/Surface/Archetypes/Tools.cs
@@ -1342,13 +1342,13 @@ namespace FlaxEditor.Surface.Archetypes
},
new NodeArchetype
{
- TypeID = 17,
+ TypeID = 18,
Title = "Platform Switch",
Description = "Gets the input value based on the runtime-platform type",
Flags = NodeFlags.AllGraphs,
Size = new Vector2(220, 200),
ConnectionsHints = ConnectionsHint.Value,
- IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
+ IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 },
DependentBoxes = new[] { 0 },
Elements = new[]
{
@@ -1363,6 +1363,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(7, "Android", true, null, 8),
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),
}
},
new NodeArchetype
diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs
index 4d9f6b3e3..61bd753af 100644
--- a/Source/Editor/Windows/GameCookerWindow.cs
+++ b/Source/Editor/Windows/GameCookerWindow.cs
@@ -45,6 +45,7 @@ namespace FlaxEditor.Windows
{ PlatformType.Android, new Android() },
{ PlatformType.Switch, new Switch() },
{ PlatformType.PS5, new PS5() },
+ { PlatformType.Mac, new Mac() },
};
public BuildTabProxy(GameCookerWindow win, PlatformSelector platformSelector)
@@ -61,6 +62,7 @@ namespace FlaxEditor.Windows
PerPlatformOptions[PlatformType.Android].Init("Output/Android", "Android");
PerPlatformOptions[PlatformType.Switch].Init("Output/Switch", "Switch");
PerPlatformOptions[PlatformType.PS5].Init("Output/PS5", "PS5");
+ PerPlatformOptions[PlatformType.Mac].Init("Output/Mac", "Mac");
}
abstract class Platform
@@ -102,7 +104,15 @@ namespace FlaxEditor.Windows
// Check if can build on that platform
#if PLATFORM_WINDOWS
- IsSupported = true;
+ switch (BuildPlatform)
+ {
+ case BuildPlatform.MacOSx64:
+ IsSupported = false;
+ break;
+ default:
+ IsSupported = true;
+ break;
+ }
#elif PLATFORM_LINUX
switch (BuildPlatform)
{
@@ -202,6 +212,11 @@ namespace FlaxEditor.Windows
protected override BuildPlatform BuildPlatform => BuildPlatform.PS5;
}
+ class Mac : Platform
+ {
+ protected override BuildPlatform BuildPlatform => BuildPlatform.MacOSx64;
+ }
+
class Editor : CustomEditor
{
private PlatformType _platform;
@@ -249,6 +264,9 @@ namespace FlaxEditor.Windows
case PlatformType.PS5:
name = "PlayStation 5";
break;
+ case PlatformType.Mac:
+ name = "Mac";
+ break;
default:
name = CustomEditorsUtil.GetPropertyNameUI(_platform.ToString());
break;
diff --git a/Source/Engine/Audio/Audio.Build.cs b/Source/Engine/Audio/Audio.Build.cs
index 694adb742..e0bb24ed3 100644
--- a/Source/Engine/Audio/Audio.Build.cs
+++ b/Source/Engine/Audio/Audio.Build.cs
@@ -54,6 +54,9 @@ public class Audio : EngineModule
options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS5", "Engine", "Audio"));
options.CompileEnv.PreprocessorDefinitions.Add("AUDIO_API_PS5");
break;
+ case TargetPlatform.Mac:
+ useNone = true; // TODO: Audio support on Mac
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp
index 6f21b9b73..482ef156c 100644
--- a/Source/Engine/Core/Config/GameSettings.cpp
+++ b/Source/Engine/Core/Config/GameSettings.cpp
@@ -65,6 +65,8 @@ IMPLEMENT_SETTINGS_GETTER(XboxScarlettPlatformSettings, XboxScarlettPlatform);
IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
#elif PLATFORM_SWITCH
IMPLEMENT_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform);
+#elif PLATFORM_MAC
+IMPLEMENT_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
#else
#error Unknown platform
#endif
@@ -226,6 +228,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE(AndroidPlatform);
DESERIALIZE(SwitchPlatform);
DESERIALIZE(PS5Platform);
+ DESERIALIZE(MacPlatform);
}
void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs
index 4c365e4b0..1b34660a0 100644
--- a/Source/Engine/Core/Config/GameSettings.cs
+++ b/Source/Engine/Core/Config/GameSettings.cs
@@ -177,6 +177,14 @@ namespace FlaxEditor.Content.Settings
public JsonAsset PS5Platform;
#endif
+#if FLAX_EDITOR || PLATFORM_MAC
+ ///
+ /// Reference to asset. Used to apply configuration on Mac platform.
+ ///
+ [EditorOrder(2090), EditorDisplay("Platform Settings", "Mac"), AssetReference(typeof(MacPlatformSettings), true), Tooltip("Reference to Mac Platform Settings asset")]
+ public JsonAsset MacPlatform;
+#endif
+
///
/// Gets the absolute path to the game settings asset file.
///
@@ -295,6 +303,10 @@ namespace FlaxEditor.Content.Settings
if (type.FullName == PS5PlatformSettingsTypename)
return LoadAsset(gameSettings.PS5Platform, PS5PlatformSettingsTypename) as T;
#endif
+#if FLAX_EDITOR || PLATFORM_MAC
+ if (type == typeof(MacPlatformSettings))
+ return LoadAsset(gameSettings.MacPlatform) as T;
+#endif
if (gameSettings.CustomSettings != null)
{
@@ -391,6 +403,8 @@ namespace FlaxEditor.Content.Settings
return SaveAsset(gameSettings, ref gameSettings.PS5Platform, obj);
if (type == typeof(AudioSettings))
return SaveAsset(gameSettings, ref gameSettings.Audio, obj);
+ if (type == typeof(MacPlatformSettings))
+ return SaveAsset(gameSettings, ref gameSettings.MacPlatform, obj);
return true;
}
diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h
index a9f324127..fe99f014c 100644
--- a/Source/Engine/Core/Config/GameSettings.h
+++ b/Source/Engine/Core/Config/GameSettings.h
@@ -82,6 +82,7 @@ public:
Guid AndroidPlatform;
Guid SwitchPlatform;
Guid PS5Platform;
+ Guid MacPlatform;
public:
diff --git a/Source/Engine/Core/Config/PlatformSettings.h b/Source/Engine/Core/Config/PlatformSettings.h
index df5dceedd..313ecf33f 100644
--- a/Source/Engine/Core/Config/PlatformSettings.h
+++ b/Source/Engine/Core/Config/PlatformSettings.h
@@ -32,3 +32,6 @@
#if PLATFORM_PS5
#include "Platforms/PS5/Engine/Platform/PS5PlatformSettings.h"
#endif
+#if PLATFORM_MAC
+#include "Engine/Platform/Mac/MacPlatformSettings.h"
+#endif
diff --git a/Source/Engine/Engine/Game.h b/Source/Engine/Engine/Game.h
index 55e1f0869..e979ebe5d 100644
--- a/Source/Engine/Engine/Game.h
+++ b/Source/Engine/Engine/Game.h
@@ -22,6 +22,8 @@
#include "Android/AndroidGame.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Engine/SwitchGame.h"
+#elif PLATFORM_MAC
+#include "Mac/MacGame.h"
#else
#error Missing Game implementation!
#endif
diff --git a/Source/Engine/Engine/Mac/MacGame.h b/Source/Engine/Engine/Mac/MacGame.h
new file mode 100644
index 000000000..e6b39a134
--- /dev/null
+++ b/Source/Engine/Engine/Mac/MacGame.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC && !USE_EDITOR
+
+#include "../Base/GameBase.h"
+
+///
+/// The game class implementation for Mac platform.
+///
+///
+class MacGame : public GameBase
+{
+};
+
+typedef MacGame Game;
+
+#endif
diff --git a/Source/Engine/Graphics/Graphics.Build.cs b/Source/Engine/Graphics/Graphics.Build.cs
index 08c9c6e83..625128ece 100644
--- a/Source/Engine/Graphics/Graphics.Build.cs
+++ b/Source/Engine/Graphics/Graphics.Build.cs
@@ -81,6 +81,9 @@ public class Graphics : EngineModule
case TargetPlatform.Switch:
options.PrivateDependencies.Add("GraphicsDeviceVulkan");
break;
+ case TargetPlatform.Mac:
+ options.PrivateDependencies.Add("GraphicsDeviceNull"); // TODO: Graphics support on Mac
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/Engine/Main/Mac/main.cpp b/Source/Engine/Main/Mac/main.cpp
new file mode 100644
index 000000000..b51ad799c
--- /dev/null
+++ b/Source/Engine/Main/Mac/main.cpp
@@ -0,0 +1,24 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "Engine/Engine/Engine.h"
+#include "Engine/Core/Types/StringBuilder.h"
+
+int main(int argc, char* argv[])
+{
+ // Join the arguments
+ StringBuilder args;
+ for (int i = 1; i < argc; i++)
+ {
+ args.Append(argv[i]);
+
+ if (i + 1 != argc)
+ args.Append(TEXT(' '));
+ }
+ args.Append(TEXT('\0'));
+
+ return Engine::Main(*args);
+}
+
+#endif
diff --git a/Source/Engine/Main/Main.Build.cs b/Source/Engine/Main/Main.Build.cs
index a40151b44..37adc09d9 100644
--- a/Source/Engine/Main/Main.Build.cs
+++ b/Source/Engine/Main/Main.Build.cs
@@ -72,6 +72,9 @@ public class Main : EngineModule
case TargetPlatform.Switch:
options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "Main"));
break;
+ case TargetPlatform.Mac:
+ options.SourcePaths.Add(Path.Combine(FolderPath, "Mac"));
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
}
diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp
index 382aff1b4..b10838a6a 100644
--- a/Source/Engine/Platform/Base/PlatformBase.cpp
+++ b/Source/Engine/Platform/Base/PlatformBase.cpp
@@ -614,6 +614,8 @@ const Char* ToString(PlatformType type)
return TEXT("Switch");
case PlatformType::PS5:
return TEXT("PlayStation 5");
+ case PlatformType::Mac:
+ return TEXT("Mac");
default:
return TEXT("");
}
diff --git a/Source/Engine/Platform/CriticalSection.h b/Source/Engine/Platform/CriticalSection.h
index 6dae6588f..f3e9c27f0 100644
--- a/Source/Engine/Platform/CriticalSection.h
+++ b/Source/Engine/Platform/CriticalSection.h
@@ -2,21 +2,9 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32CriticalSection.h"
-#elif PLATFORM_UWP
-#include "Win32/Win32CriticalSection.h"
-#elif PLATFORM_LINUX
-#include "Unix/UnixCriticalSection.h"
-#elif PLATFORM_PS4
-#include "Unix/UnixCriticalSection.h"
-#elif PLATFORM_PS5
-#include "Unix/UnixCriticalSection.h"
-#elif PLATFORM_XBOX_ONE
-#include "Win32/Win32CriticalSection.h"
-#elif PLATFORM_XBOX_SCARLETT
-#include "Win32/Win32CriticalSection.h"
-#elif PLATFORM_ANDROID
+#elif PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5
#include "Unix/UnixCriticalSection.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchCriticalSection.h"
diff --git a/Source/Engine/Platform/Defines.h b/Source/Engine/Platform/Defines.h
index 2ac7f8e1f..f7f8ddf35 100644
--- a/Source/Engine/Platform/Defines.h
+++ b/Source/Engine/Platform/Defines.h
@@ -53,6 +53,11 @@ API_ENUM() enum class PlatformType
/// Running on PlayStation 5.
///
PS5 = 9,
+
+ ///
+ /// Running on Mac.
+ ///
+ Mac = 10,
};
///
@@ -120,9 +125,6 @@ API_ENUM() enum class ArchitectureType
#if !defined(PLATFORM_MAC)
#define PLATFORM_MAC 0
#endif
-#if !defined(PLATFORM_OSX)
-#define PLATFORM_OSX 0
-#endif
#if !defined(PLATFORM_IOS)
#define PLATFORM_IOS 0
#endif
@@ -148,6 +150,8 @@ API_ENUM() enum class ArchitectureType
#include "Android/AndroidDefines.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchDefines.h"
+#elif PLATFORM_MAC
+#include "Mac/MacDefines.h"
#else
#error Missing Defines implementation!
#endif
@@ -189,7 +193,7 @@ API_ENUM() enum class ArchitectureType
#define PLATFORM_WINDOWS_FAMILY (PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT)
#define PLATFORM_MICROSOFT_FAMILY (PLATFORM_WINDOWS_FAMILY)
#define PLATFORM_UNIX_FAMILY (PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_PS4 || PLATFORM_PS5)
-#define PLATFORM_APPLE_FAMILY (PLATFORM_IOS || PLATFORM_OSX)
+#define PLATFORM_APPLE_FAMILY (PLATFORM_MAC || PLATFORM_IOS)
// SIMD defines
#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) || defined(__SSE2__)
diff --git a/Source/Engine/Platform/File.h b/Source/Engine/Platform/File.h
index 98696040b..d7df5444c 100644
--- a/Source/Engine/Platform/File.h
+++ b/Source/Engine/Platform/File.h
@@ -2,24 +2,16 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32File.h"
-#elif PLATFORM_UWP
-#include "Win32/Win32File.h"
-#elif PLATFORM_LINUX
+#elif PLATFORM_LINUX || PLATFORM_PS4 || PLATFORM_PS5
#include "Unix/UnixFile.h"
-#elif PLATFORM_PS4
-#include "Unix/UnixFile.h"
-#elif PLATFORM_PS5
-#include "Unix/UnixFile.h"
-#elif PLATFORM_XBOX_ONE
-#include "Win32/Win32File.h"
-#elif PLATFORM_XBOX_SCARLETT
-#include "Win32/Win32File.h"
#elif PLATFORM_ANDROID
#include "Android/AndroidFile.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchFile.h"
+#elif PLATFORM_MAC
+#include "Mac/MacFile.h"
#else
#error Missing File implementation!
#endif
diff --git a/Source/Engine/Platform/FileSystem.h b/Source/Engine/Platform/FileSystem.h
index c64be0cf7..e17552db4 100644
--- a/Source/Engine/Platform/FileSystem.h
+++ b/Source/Engine/Platform/FileSystem.h
@@ -20,6 +20,8 @@
#include "Android/AndroidFileSystem.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchFileSystem.h"
+#elif PLATFORM_MAC
+#include "Mac/MacFileSystem.h"
#else
#error Missing File System implementation!
#endif
diff --git a/Source/Engine/Platform/Mac/MacConditionVariable.h b/Source/Engine/Platform/Mac/MacConditionVariable.h
new file mode 100644
index 000000000..c1d60184d
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacConditionVariable.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "MacCriticalSection.h"
+#include
+#include
+
+///
+/// Mac implementation of a condition variables. Condition variables are synchronization primitives that enable threads to wait until a particular condition occurs. Condition variables enable threads to atomically release a lock and enter the sleeping state.
+///
+class FLAXENGINE_API MacConditionVariable
+{
+private:
+
+ pthread_cond_t _cond;
+
+private:
+
+ MacConditionVariable(const MacConditionVariable&);
+ MacConditionVariable& operator=(const MacConditionVariable&);
+
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ MacConditionVariable()
+ {
+ pthread_cond_init(&_cond, nullptr);
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~MacConditionVariable()
+ {
+ pthread_cond_destroy(&_cond);
+ }
+
+public:
+
+ ///
+ /// Blocks the current thread execution until the condition variable is woken up.
+ ///
+ /// The critical section locked by the current thread.
+ void Wait(const MacCriticalSection& lock)
+ {
+ pthread_cond_wait(&_cond, lock._mutexPtr);
+ }
+
+ ///
+ /// Blocks the current thread execution until the condition variable is woken up or after the specified timeout duration.
+ ///
+ /// The critical section locked by the current thread.
+ /// The time-out interval, in milliseconds. If the time-out interval elapses, the function re-acquires the critical section and returns zero. If timeout is zero, the function tests the states of the specified objects and returns immediately. If timeout is INFINITE, the function's time-out interval never elapses.
+ /// If the function succeeds, the return value is true, otherwise, if the function fails or the time-out interval elapses, the return value is false.
+ bool Wait(const MacCriticalSection& lock, const int32 timeout)
+ {
+ struct timeval tv;
+ struct timespec ts;
+
+ gettimeofday(&tv, NULL);
+ ts.tv_sec = time(NULL) + timeout / 1000;
+ ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * (timeout % 1000);
+ ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
+ ts.tv_nsec %= (1000 * 1000 * 1000);
+
+ return pthread_cond_timedwait(&_cond, lock._mutexPtr, &ts) == 0;
+ }
+
+ ///
+ /// Notifies one waiting thread.
+ ///
+ void NotifyOne()
+ {
+ pthread_cond_signal(&_cond);
+ }
+
+ ///
+ /// Notifies all waiting threads.
+ ///
+ void NotifyAll()
+ {
+ pthread_cond_broadcast(&_cond);
+ }
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacCriticalSection.h b/Source/Engine/Platform/Mac/MacCriticalSection.h
new file mode 100644
index 000000000..2f83260f1
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacCriticalSection.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "Engine/Platform/Platform.h"
+#include
+
+class MacConditionVariable;
+
+///
+/// Mac implementation of a critical section.
+///
+class FLAXENGINE_API MacCriticalSection
+{
+ friend MacConditionVariable;
+
+private:
+
+ pthread_mutex_t _mutex;
+ pthread_mutex_t* _mutexPtr;
+#if BUILD_DEBUG
+ pthread_t _owningThreadId;
+#endif
+
+private:
+
+ MacCriticalSection(const MacCriticalSection&);
+ MacCriticalSection& operator=(const MacCriticalSection&);
+
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ MacCriticalSection()
+ {
+ pthread_mutexattr_t attributes;
+ pthread_mutexattr_init(&attributes);
+ pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&_mutex, &attributes);
+ pthread_mutexattr_destroy(&attributes);
+ _mutexPtr = &_mutex;
+#if BUILD_DEBUG
+ _owningThreadId = 0;
+#endif
+ }
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~MacCriticalSection()
+ {
+ pthread_mutex_destroy(&_mutex);
+ }
+
+public:
+
+ ///
+ /// Locks the critical section.
+ ///
+ void Lock() const
+ {
+ pthread_mutex_lock(_mutexPtr);
+#if BUILD_DEBUG
+ ((MacCriticalSection*)this)->_owningThreadId = pthread_self();
+#endif
+ }
+
+ ///
+ /// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
+ ///
+ /// True if calling thread took ownership of the critical section.
+ bool TryLock() const
+ {
+ return pthread_mutex_trylock(_mutexPtr) == 0;
+ }
+
+ ///
+ /// Releases the lock on the critical section.
+ ///
+ void Unlock() const
+ {
+#if BUILD_DEBUG
+ ((MacCriticalSection*)this)->_owningThreadId = 0;
+#endif
+ pthread_mutex_unlock(_mutexPtr);
+ }
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacDefines.h b/Source/Engine/Platform/Mac/MacDefines.h
new file mode 100644
index 000000000..c56284643
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacDefines.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+// Platform description
+#define PLATFORM_64BITS 1
+#define PLATFORM_ARCH_X64 1
+#define PLATFORM_ARCH ArchitectureType::x64
+#define PLATFORM_TYPE PlatformType::Mac
+#define PLATFORM_DESKTOP 1
+#define PLATFORM_CACHE_LINE_SIZE 128
+#define PLATFORM_HAS_HEADLESS_MODE 1
+#define PLATFORM_DEBUG_BREAK __builtin_trap()
+#define PLATFORM_LINE_TERMINATOR "\n"
+#define PLATFORM_TEXT_IS_CHAR16 1
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacFile.cpp b/Source/Engine/Platform/Mac/MacFile.cpp
new file mode 100644
index 000000000..109306ad7
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacFile.cpp
@@ -0,0 +1,156 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "../File.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/DateTime.h"
+#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Utilities/StringConverter.h"
+#include "Engine/Core/Log.h"
+#include
+#include
+#include
+#include
+#include
+
+MacFile::MacFile(int32 handle)
+ : _handle(handle)
+{
+}
+
+MacFile::~MacFile()
+{
+ Close();
+}
+
+MacFile* MacFile::Open(const StringView& path, FileMode mode, FileAccess access, FileShare share)
+{
+ int flags = O_CLOEXEC;
+ switch (access)
+ {
+ case FileAccess::Read:
+ flags |= O_RDONLY;
+ break;
+ case FileAccess::Write:
+ flags |= O_WRONLY;
+ break;
+ case FileAccess::ReadWrite:
+ flags |= O_RDWR;
+ break;
+ default: ;
+ }
+ switch (mode)
+ {
+ case FileMode::CreateAlways:
+ flags |= O_CREAT | O_TRUNC;
+ break;
+ case FileMode::CreateNew:
+ flags |= O_CREAT | O_EXCL;
+ break;
+ case FileMode::OpenAlways:
+ break;
+ case FileMode::OpenExisting:
+ break;
+ case FileMode::TruncateExisting:
+ flags |= O_TRUNC;
+ break;
+ default: ;
+ }
+
+ mode_t omode = S_IRUSR | S_IWUSR;
+ if ((uint32)share & (uint32)FileShare::Delete)
+ omode |= 0;
+ if ((uint32)share & (uint32)FileShare::Read)
+ omode |= (mode_t)(S_IRGRP | S_IROTH);
+ if ((uint32)share & (uint32)FileShare::Write)
+ omode |= (mode_t)(S_IWGRP | S_IWOTH);
+ if ((uint32)share & (uint32)FileShare::Delete)
+ omode |= 0;
+
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ auto handle = open(pathANSI.Get(), flags, omode);
+ if (handle == -1)
+ {
+ LOG_Mac_LAST_ERROR;
+ return nullptr;
+ }
+
+ return New(handle);
+}
+
+bool MacFile::Read(void* buffer, uint32 bytesToRead, uint32* bytesRead)
+{
+ const ssize_t tmp = read(_handle, buffer, bytesToRead);
+ if (tmp != -1)
+ {
+ if (bytesRead)
+ *bytesRead = tmp;
+ return false;
+ }
+ if (bytesRead)
+ *bytesRead = 0;
+ LOG_Mac_LAST_ERROR;
+ return true;
+}
+
+bool MacFile::Write(const void* buffer, uint32 bytesToWrite, uint32* bytesWritten)
+{
+ const ssize_t tmp = write(_handle, buffer, bytesToWrite);
+ if (tmp != -1)
+ {
+ if (bytesWritten)
+ *bytesWritten = tmp;
+ return false;
+ }
+ if (bytesWritten)
+ *bytesWritten = 0;
+ LOG_Mac_LAST_ERROR;
+ return true;
+}
+
+void MacFile::Close()
+{
+ if (_handle != -1)
+ {
+ close(_handle);
+ _handle = -1;
+ }
+}
+
+uint32 MacFile::GetSize() const
+{
+ struct stat fileInfo;
+ fstat(_handle, &fileInfo);
+ return fileInfo.st_size;
+}
+
+DateTime MacFile::GetLastWriteTime() const
+{
+ struct stat fileInfo;
+ if (fstat(_handle, &fileInfo) == -1)
+ {
+ return DateTime::MinValue();
+ }
+ const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
+ const DateTime MacEpoch(1970, 1, 1);
+ return MacEpoch + timeSinceEpoch;
+}
+
+uint32 MacFile::GetPosition() const
+{
+ return lseek(_handle, 0, SEEK_CUR);
+}
+
+void MacFile::SetPosition(uint32 seek)
+{
+ lseek(_handle, seek, SEEK_SET);
+}
+
+bool MacFile::IsOpened() const
+{
+ return _handle != -1;
+}
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacFile.h b/Source/Engine/Platform/Mac/MacFile.h
new file mode 100644
index 000000000..8e6741db1
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacFile.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "Engine/Platform/Base/FileBase.h"
+
+///
+/// Mac platform file object implementation.
+///
+class MacFile : public FileBase
+{
+protected:
+
+ int32 _handle;
+
+public:
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The handle.
+ MacFile(int32 handle);
+
+ ///
+ /// Finalizes an instance of the class.
+ ///
+ ~MacFile();
+
+public:
+
+ ///
+ /// Creates or opens a file.
+ ///
+ /// The name of the file to be created or opened.
+ /// An action to take on a file that exists or does not exist.
+ /// The requested access to the file.
+ /// The requested sharing mode of the file.
+ /// Opened file handle or null if cannot.
+ static MacFile* Open(const StringView& path, FileMode mode, FileAccess access = FileAccess::ReadWrite, FileShare share = FileShare::None);
+
+public:
+
+ // [FileBase]
+ bool Read(void* buffer, uint32 bytesToRead, uint32* bytesRead = nullptr) override;
+ bool Write(const void* buffer, uint32 bytesToWrite, uint32* bytesWritten = nullptr) override;
+ void Close() override;
+ uint32 GetSize() const override;
+ DateTime GetLastWriteTime() const override;
+ uint32 GetPosition() const override;
+ void SetPosition(uint32 seek) override;
+ bool IsOpened() const override;
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacFileSystem.cpp b/Source/Engine/Platform/Mac/MacFileSystem.cpp
new file mode 100644
index 000000000..165fa2bfb
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacFileSystem.cpp
@@ -0,0 +1,523 @@
+// Copyright (c) 2012-2019 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "MacFileSystem.h"
+#include "Engine/Platform/File.h"
+#include "Engine/Core/Types/String.h"
+#include "Engine/Core/Types/StringView.h"
+#include "Engine/Core/Types/TimeSpan.h"
+#include "Engine/Core/Math/Math.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Utilities/StringConverter.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+const DateTime UnixEpoch(1970, 1, 1);
+
+bool MacFileSystem::CreateDirectory(const StringView& path)
+{
+ const StringAsANSI<> pathAnsi(*path, path.Length());
+
+ // Skip if already exists
+ struct stat fileInfo;
+ if (stat(pathAnsi.Get(), &fileInfo) != -1 && S_ISDIR(fileInfo.st_mode))
+ {
+ return false;
+ }
+
+ // Recursively do it all again for the parent directory, if any
+ const int32 slashIndex = path.FindLast('/');
+ if (slashIndex > 1)
+ {
+ if (CreateDirectory(path.Substring(0, slashIndex)))
+ {
+ return true;
+ }
+ }
+
+ // Create the last directory on the path (the recursive calls will have taken care of the parent directories by now)
+ return mkdir(pathAnsi.Get(), 0755) != 0 && errno != EEXIST;
+}
+
+bool DeletePathTree(const char* path)
+{
+ size_t pathLength;
+ DIR* dir;
+ struct stat statPath, statEntry;
+ struct dirent* entry;
+
+ // Stat for the path
+ stat(path, &statPath);
+
+ // If path does not exists or is not dir - exit with status -1
+ if (S_ISDIR(statPath.st_mode) == 0)
+ {
+ // Is not directory
+ return true;
+ }
+
+ // If not possible to read the directory for this user
+ if ((dir = opendir(path)) == NULL)
+ {
+ // Cannot open directory
+ return true;
+ }
+
+ // The length of the path
+ pathLength = strlen(path);
+
+ // Iteration through entries in the directory
+ while ((entry = readdir(dir)) != NULL)
+ {
+ // Skip entries "." and ".."
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
+ continue;
+
+ // Determinate a full path of an entry
+ char full_path[256];
+ ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
+ strcpy(full_path, path);
+ strcat(full_path, "/");
+ strcat(full_path, entry->d_name);
+
+ // Stat for the entry
+ stat(full_path, &statEntry);
+
+ // Recursively remove a nested directory
+ if (S_ISDIR(statEntry.st_mode) != 0)
+ {
+ if (DeletePathTree(full_path))
+ return true;
+ continue;
+ }
+
+ // Remove a file object
+ if (unlink(full_path) != 0)
+ return true;
+ }
+
+ // Remove the devastated directory and close the object of it
+ if (rmdir(path) != 0)
+ return true;
+
+ closedir(dir);
+
+ return false;
+}
+
+bool MacFileSystem::DeleteDirectory(const String& path, bool deleteContents)
+{
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ if (deleteContents)
+ return DeletePathTree(pathANSI.Get());
+ return rmdir(pathANSI.Get()) != 0;
+}
+
+bool MacFileSystem::DirectoryExists(const StringView& path)
+{
+ struct stat fileInfo;
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ if (stat(pathANSI.Get(), &fileInfo) != -1)
+ {
+ return S_ISDIR(fileInfo.st_mode);
+ }
+ return false;
+}
+
+bool MacFileSystem::DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option)
+{
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ const StringAsANSI<> searchPatternANSI(searchPattern);
+
+ // Check if use only top directory
+ if (option == DirectorySearchOption::TopDirectoryOnly)
+ return getFilesFromDirectoryTop(results, pathANSI.Get(), searchPatternANSI.Get());
+ return getFilesFromDirectoryAll(results, pathANSI.Get(), searchPatternANSI.Get());
+}
+
+bool MacFileSystem::GetChildDirectories(Array& results, const String& directory)
+{
+ size_t pathLength;
+ DIR* dir;
+ struct stat statPath, statEntry;
+ struct dirent* entry;
+ const StringAsANSI<> pathANSI(*directory, directory.Length());
+ const char* path = pathANSI.Get();
+
+ // Stat for the path
+ stat(path, &statPath);
+
+ // If path does not exists or is not dir - exit with status -1
+ if (S_ISDIR(statPath.st_mode) == 0)
+ {
+ // Is not directory
+ return true;
+ }
+
+ // If not possible to read the directory for this user
+ if ((dir = opendir(path)) == NULL)
+ {
+ // Cannot open directory
+ return true;
+ }
+
+ // The length of the path
+ pathLength = strlen(path);
+
+ // Iteration through entries in the directory
+ while ((entry = readdir(dir)) != NULL)
+ {
+ // Skip entries "." and ".."
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
+ continue;
+
+ // Determinate a full path of an entry
+ char full_path[256];
+ ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
+ strcpy(full_path, path);
+ strcat(full_path, "/");
+ strcat(full_path, entry->d_name);
+
+ // Stat for the entry
+ stat(full_path, &statEntry);
+
+ // Check for directory
+ if (S_ISDIR(statEntry.st_mode) != 0)
+ {
+ // Add directory
+ results.Add(String(full_path));
+ }
+ }
+
+ closedir(dir);
+
+ return false;
+}
+
+bool MacFileSystem::FileExists(const StringView& path)
+{
+ struct stat fileInfo;
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ if (stat(pathANSI.Get(), &fileInfo) != -1)
+ {
+ return S_ISREG(fileInfo.st_mode);
+ }
+ return false;
+}
+
+bool MacFileSystem::DeleteFile(const StringView& path)
+{
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ return unlink(pathANSI.Get()) == 0;
+}
+
+uint64 MacFileSystem::GetFileSize(const StringView& path)
+{
+ struct stat fileInfo;
+ fileInfo.st_size = -1;
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ if (stat(pathANSI.Get(), &fileInfo) != -1)
+ {
+ // Check for directories
+ if (S_ISDIR(fileInfo.st_mode))
+ {
+ fileInfo.st_size = -1;
+ }
+ }
+ return fileInfo.st_size;
+}
+
+bool MacFileSystem::IsReadOnly(const StringView& path)
+{
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ if (access(pathANSI.Get(), W_OK) == -1)
+ {
+ return errno == EACCES;
+ }
+ return false;
+}
+
+bool MacFileSystem::SetReadOnly(const StringView& path, bool isReadOnly)
+{
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ struct stat fileInfo;
+ if (stat(pathANSI.Get(), &fileInfo) != -1)
+ {
+ if (isReadOnly)
+ {
+ fileInfo.st_mode &= ~S_IWUSR;
+ }
+ else
+ {
+ fileInfo.st_mode |= S_IWUSR;
+ }
+ return chmod(pathANSI.Get(), fileInfo.st_mode) == 0;
+ }
+ return false;
+}
+
+bool MacFileSystem::MoveFile(const StringView& dst, const StringView& src, bool overwrite)
+{
+ if (!overwrite && FileExists(dst))
+ {
+ // Already exists
+ return true;
+ }
+
+ if (overwrite)
+ {
+ unlink(StringAsANSI<>(*dst, dst.Length()).Get());
+ }
+ if (rename(StringAsANSI<>(*src, src.Length()).Get(), StringAsANSI<>(*dst, dst.Length()).Get()) != 0)
+ {
+ if (errno == EXDEV)
+ {
+ if (!CopyFile(dst, src))
+ {
+ unlink(StringAsANSI<>(*src, src.Length()).Get());
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool MacFileSystem::CopyFile(const StringView& dst, const StringView& src)
+{
+ const StringAsANSI<> srcANSI(*src, src.Length());
+ const StringAsANSI<> dstANSI(*dst, dst.Length());
+
+ int srcFile, dstFile;
+ char buffer[4096];
+ ssize_t readSize;
+ int cachedError;
+
+ srcFile = open(srcANSI.Get(), O_RDONLY);
+ if (srcFile < 0)
+ return true;
+ dstFile = open(dstANSI.Get(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (dstFile < 0)
+ goto out_error;
+
+ // first try the kernel method
+ struct stat statBuf;
+ fstat(srcFile, &statBuf);
+ readSize = 1;
+ while (readSize > 0)
+ {
+ readSize = sendfile(dstFile, srcFile, 0, statBuf.st_size);
+ }
+ // sendfile could fail for example if the input file is not nmap'able
+ // in this case we fall back to the read/write loop
+ if (readSize < 0)
+ {
+ while (readSize = read(srcFile, buffer, sizeof(buffer)), readSize > 0)
+ {
+ char* ptr = buffer;
+ ssize_t writeSize;
+
+ do
+ {
+ writeSize = write(dstFile, ptr, readSize);
+ if (writeSize >= 0)
+ {
+ readSize -= writeSize;
+ ptr += writeSize;
+ }
+ else if (errno != EINTR)
+ {
+ goto out_error;
+ }
+ } while (readSize > 0);
+ }
+ }
+
+ if (readSize == 0)
+ {
+ if (close(dstFile) < 0)
+ {
+ dstFile = -1;
+ goto out_error;
+ }
+ close(srcFile);
+
+ // Success
+ return false;
+ }
+
+out_error:
+ cachedError = errno;
+ close(srcFile);
+ if (dstFile >= 0)
+ close(dstFile);
+ errno = cachedError;
+ return true;
+}
+
+bool MacFileSystem::getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern)
+{
+ size_t pathLength;
+ struct stat statPath, statEntry;
+ struct dirent* entry;
+
+ // Stat for the path
+ stat(path, &statPath);
+
+ // If path does not exists or is not dir - exit with status -1
+ if (S_ISDIR(statPath.st_mode) == 0)
+ {
+ // Is not directory
+ return true;
+ }
+
+ // If not possible to read the directory for this user
+ DIR* dir = opendir(path);
+ if (dir == NULL)
+ {
+ // Cannot open directory
+ return true;
+ }
+
+ // The length of the path
+ pathLength = strlen(path);
+
+ // Iteration through entries in the directory
+ while ((entry = readdir(dir)) != NULL)
+ {
+ // Skip entries "." and ".."
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
+ continue;
+
+ // Determinate a full path of an entry
+ char fullPath[256];
+ ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(fullPath));
+ strcpy(fullPath, path);
+ strcat(fullPath, "/");
+ strcat(fullPath, entry->d_name);
+
+ // Stat for the entry
+ stat(fullPath, &statEntry);
+
+ // Check for file
+ if (S_ISREG(statEntry.st_mode) != 0)
+ {
+ // Validate with filter
+ const int32 fullPathLength = StringUtils::Length(fullPath);
+ const int32 searchPatternLength = StringUtils::Length(searchPattern);
+ if (searchPatternLength == 0 || StringUtils::Compare(searchPattern, "*") == 0)
+ {
+ // All files
+ }
+ else if (searchPattern[0] == '*' && searchPatternLength < fullPathLength && StringUtils::Compare(fullPath + fullPathLength - searchPatternLength + 1, searchPattern + 1, searchPatternLength - 1) == 0)
+ {
+ // Path ending
+ }
+ else
+ {
+ // TODO: implement all cases in a generic way
+ continue;
+ }
+
+ // Add file
+ results.Add(String(fullPath));
+ }
+ }
+
+ closedir(dir);
+
+ return false;
+}
+
+bool MacFileSystem::getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern)
+{
+ // Find all files in this directory
+ getFilesFromDirectoryTop(results, path, searchPattern);
+
+ size_t pathLength;
+ DIR* dir;
+ struct stat statPath, statEntry;
+ struct dirent* entry;
+
+ // Stat for the path
+ stat(path, &statPath);
+
+ // If path does not exists or is not dir - exit with status -1
+ if (S_ISDIR(statPath.st_mode) == 0)
+ {
+ // Is not directory
+ return true;
+ }
+
+ // If not possible to read the directory for this user
+ if ((dir = opendir(path)) == NULL)
+ {
+ // Cannot open directory
+ return true;
+ }
+
+ // The length of the path
+ pathLength = strlen(path);
+
+ // Iteration through entries in the directory
+ while ((entry = readdir(dir)) != NULL)
+ {
+ // Skip entries "." and ".."
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
+ continue;
+
+ // Determinate a full path of an entry
+ char full_path[256];
+ ASSERT(pathLength + strlen(entry->d_name) < ARRAY_COUNT(full_path));
+ strcpy(full_path, path);
+ strcat(full_path, "/");
+ strcat(full_path, entry->d_name);
+
+ // Stat for the entry
+ stat(full_path, &statEntry);
+
+ // Check for directory
+ if (S_ISDIR(statEntry.st_mode) != 0)
+ {
+ if (getFilesFromDirectoryAll(results, full_path, searchPattern))
+ {
+ closedir(dir);
+ return true;
+ }
+ }
+ }
+
+ closedir(dir);
+
+ return false;
+}
+
+DateTime MacFileSystem::GetFileLastEditTime(const StringView& path)
+{
+ struct stat fileInfo;
+ const StringAsANSI<> pathANSI(*path, path.Length());
+ if (stat(pathANSI.Get(), &fileInfo) == -1)
+ {
+ return DateTime::MinValue();
+ }
+
+ const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
+ return UnixEpoch + timeSinceEpoch;
+}
+
+void MacFileSystem::GetSpecialFolderPath(const SpecialFolder type, String& result)
+{
+ MISSING_CODE("MacFileSystem::GetSpecialFolderPath");
+ return; // TODO: filesystem on Mac
+}
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacFileSystem.h b/Source/Engine/Platform/Mac/MacFileSystem.h
new file mode 100644
index 000000000..4ad8df0b6
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacFileSystem.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "Engine/Platform/Base/FileSystemBase.h"
+
+///
+/// Mac platform implementation of filesystem service.
+///
+class FLAXENGINE_API MacFileSystem : public FileSystemBase
+{
+public:
+
+ // [FileSystemBase]
+ static bool CreateDirectory(const StringView& path);
+ static bool DeleteDirectory(const String& path, bool deleteContents = true);
+ static bool DirectoryExists(const StringView& path);
+ static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option = DirectorySearchOption::AllDirectories);
+ static bool GetChildDirectories(Array& results, const String& directory);
+ static bool FileExists(const StringView& path);
+ static bool DeleteFile(const StringView& path);
+ static uint64 GetFileSize(const StringView& path);
+ static bool IsReadOnly(const StringView& path);
+ static bool SetReadOnly(const StringView& path, bool isReadOnly);
+ static bool MoveFile(const StringView& dst, const StringView& src, bool overwrite = false);
+ static bool CopyFile(const StringView& dst, const StringView& src);
+
+public:
+
+ ///
+ /// Gets last time when file has been modified (in UTC).
+ ///
+ /// The file path to check.
+ /// The last write time or DateTime::MinValue() if cannot get data.
+ static DateTime GetFileLastEditTime(const StringView& path);
+
+ ///
+ /// Gets the special folder path.
+ ///
+ /// The folder type.
+ /// The result full path.
+ static void GetSpecialFolderPath(const SpecialFolder type, String& result);
+
+private:
+
+ static bool getFilesFromDirectoryTop(Array& results, const char* path, const char* searchPattern);
+ static bool getFilesFromDirectoryAll(Array& results, const char* path, const char* searchPattern);
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacNetwork.cpp b/Source/Engine/Platform/Mac/MacNetwork.cpp
new file mode 100644
index 000000000..11eeccb26
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacNetwork.cpp
@@ -0,0 +1,7 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+// TODO: networking on Mac
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacNetwork.h b/Source/Engine/Platform/Mac/MacNetwork.h
new file mode 100644
index 000000000..1e8c16cd3
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacNetwork.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "Engine/Platform/Base/NetworkBase.h"
+
+class FLAXENGINE_API MacNetwork : public NetworkBase
+{
+public:
+
+ // [NetworkBase]
+ static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv);
+ static bool DestroySocket(NetworkSocket& socket);
+ static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value);
+ static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32& value);
+ static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint);
+ static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint);
+ static bool Listen(NetworkSocket& socket, uint16 queueSize);
+ static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint);
+ static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr);
+ static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr);
+ static bool CreateEndPoint(const String& address, const String& port, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true);
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacPlatform.cpp b/Source/Engine/Platform/Mac/MacPlatform.cpp
new file mode 100644
index 000000000..f900b6cb3
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacPlatform.cpp
@@ -0,0 +1,357 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "MacPlatform.h"
+#include "MacWindow.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/MessageBox.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 "Engine/Input/Input.h"
+#include "Engine/Input/Mouse.h"
+#include "Engine/Input/Keyboard.h"
+
+CPUInfo MacCpu;
+Guid DeviceId;
+String UserLocale, ComputerName;
+byte MacAddress[6];
+
+DialogResult MessageBox::Show(Window* parent, const StringView& text, const StringView& caption, MessageBoxButtons buttons, MessageBoxIcon icon)
+{
+ if (CommandLine::Options.Headless)
+ return DialogResult::None;
+ todo;
+}
+
+class MacKeyboard : public Keyboard
+{
+public:
+ explicit MacKeyboard()
+ : Keyboard()
+ {
+ }
+};
+
+class MacMouse : public Mouse
+{
+public:
+ explicit MacMouse()
+ : Mouse()
+ {
+ }
+
+public:
+
+ // [Mouse]
+ void SetMousePosition(const Vector2& newPosition) final override
+ {
+ MacPlatform::SetMousePosition(newPosition);
+
+ OnMouseMoved(newPosition);
+ }
+};
+
+bool MacPlatform::Is64BitPlatform()
+{
+ return PLATFORM_64BITS;
+}
+
+CPUInfo MacPlatform::GetCPUInfo()
+{
+ return MacCpu;
+}
+
+int32 MacPlatform::GetCacheLineSize()
+{
+ return MacCpu.CacheLineSize;
+}
+
+MemoryStats MacPlatform::GetMemoryStats()
+{
+ MISSING_CODE("MacPlatform::GetMemoryStats");
+ return MemoryStats(); // TODO: platform stats on Mac
+}
+
+ProcessMemoryStats MacPlatform::GetProcessMemoryStats()
+{
+ MISSING_CODE("MacPlatform::GetProcessMemoryStats");
+ return ProcessMemoryStats(); // TODO: platform stats on Mac
+}
+
+uint64 MacPlatform::GetCurrentThreadID()
+{
+ MISSING_CODE("MacPlatform::GetCurrentThreadID");
+ return 0; // TODO: threading on Mac
+}
+
+void MacPlatform::SetThreadPriority(ThreadPriority priority)
+{
+ // TODO: impl this
+}
+
+void MacPlatform::SetThreadAffinityMask(uint64 affinityMask)
+{
+ // TODO: impl this
+}
+
+void MacPlatform::Sleep(int32 milliseconds)
+{
+ MISSING_CODE("MacPlatform::Sleep");
+ return; // TODO: clock on Mac
+}
+
+double MacPlatform::GetTimeSeconds()
+{
+ MISSING_CODE("MacPlatform::GetTimeSeconds");
+ return 0.0; // TODO: clock on Mac
+}
+
+uint64 MacPlatform::GetTimeCycles()
+{
+ MISSING_CODE("MacPlatform::GetTimeCycles");
+ return 0; // TODO: clock on Mac
+}
+
+uint64 MacPlatform::GetClockFrequency()
+{
+ MISSING_CODE("MacPlatform::GetClockFrequency");
+ return 0; // TODO: clock on Mac
+}
+
+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 (PlatformBase::Init())
+ return true;
+
+ // TODO: get MacCpu
+
+ // TODO: get MacAddress
+ // TODO: get DeviceId
+
+ // TODO: get username
+ OnPlatformUserAdd(New(TEXT("User")));
+
+ // TODO: get UserLocale
+ // TODO: get ComputerName
+
+ Input::Mouse = Impl::Mouse = New();
+ Input::Keyboard = Impl::Keyboard = New();
+
+ return false;
+}
+
+void MacPlatform::BeforeRun()
+{
+}
+
+void MacPlatform::Tick()
+{
+ // TODO: app events
+}
+
+void MacPlatform::BeforeExit()
+{
+}
+
+void MacPlatform::Exit()
+{
+}
+
+int32 MacPlatform::GetDpi()
+{
+ return todo;
+}
+
+String MacPlatform::GetUserLocaleName()
+{
+ return UserLocale;
+}
+
+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();
+}
+
+bool MacPlatform::CanOpenUrl(const StringView& url)
+{
+ return false;
+}
+
+void MacPlatform::OpenUrl(const StringView& url)
+{
+}
+
+Vector2 MacPlatform::GetMousePosition()
+{
+ MISSING_CODE("MacPlatform::GetMousePosition");
+ return Vector2(0, 0); // TODO: mouse on Mac
+}
+
+void MacPlatform::SetMousePosition(const Vector2& pos)
+{
+ MISSING_CODE("MacPlatform::SetMousePosition");
+ // TODO: mouse on Mac
+}
+
+Vector2 MacPlatform::GetDesktopSize()
+{
+ MISSING_CODE("MacPlatform::GetDesktopSize");
+ return Vector2(0, 0); // TODO: desktop size on Mac
+}
+
+Rectangle MacPlatform::GetMonitorBounds(const Vector2& screenPos)
+{
+ // TODO: do it in a proper way
+ return Rectangle(Vector2::Zero, GetDesktopSize());
+}
+
+Rectangle MacPlatform::GetVirtualDesktopBounds()
+{
+ // TODO: do it in a proper way
+ return Rectangle(Vector2::Zero, GetDesktopSize());
+}
+
+String MacPlatform::GetMainDirectory()
+{
+ MISSING_CODE("MacPlatform::GetMainDirectory");
+ return TEXT("/"); // TODO: GetMainDirectory
+}
+
+String MacPlatform::GetExecutableFilePath()
+{
+ MISSING_CODE("MacPlatform::GetExecutableFilePath");
+ return TEXT("/"); // TODO: GetMainDirectory
+}
+
+Guid MacPlatform::GetUniqueDeviceId()
+{
+ return DeviceId;
+}
+
+String MacPlatform::GetWorkingDirectory()
+{
+ char buffer[256];
+ getcwd(buffer, ARRAY_COUNT(buffer));
+ return String(buffer);
+}
+
+bool MacPlatform::SetWorkingDirectory(const String& path)
+{
+ return chdir(StringAsANSI<>(*path).Get()) != 0;
+}
+
+Window* MacPlatform::CreateWindow(const CreateWindowSettings& settings)
+{
+ return New(settings);
+}
+
+bool MacPlatform::GetEnvironmentVariable(const String& name, String& value)
+{
+ char* env = getenv(StringAsANSI<>(*name).Get());
+ if (env)
+ {
+ value = String(env);
+ return false;
+ }
+ return true;
+}
+
+bool MacPlatform::SetEnvironmentVariable(const String& name, const String& value)
+{
+ return setenv(StringAsANSI<>(*name).Get(), StringAsANSI<>(*value).Get(), true) != 0;
+}
+
+void* MacPlatform::LoadLibrary(const Char* filename)
+{
+ MISSING_CODE("MacPlatform::LoadLibrary");
+ return nullptr; // TODO: dynamic libs on Mac
+}
+
+void MacPlatform::FreeLibrary(void* handle)
+{
+ MISSING_CODE("MacPlatform::FreeLibrary");
+ return; // TODO: dynamic libs on Mac
+}
+
+void* MacPlatform::GetProcAddress(void* handle, const char* symbol)
+{
+ MISSING_CODE("MacPlatform::GetProcAddress");
+ return nullptr; // TODO: dynamic libs on Mac
+}
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacPlatform.h b/Source/Engine/Platform/Mac/MacPlatform.h
new file mode 100644
index 000000000..86bb07e63
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacPlatform.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "../Base/PlatformBase.h"
+
+///
+/// The Mac platform implementation and application management utilities.
+///
+class FLAXENGINE_API MacPlatform : public UnixPlatform
+{
+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)
+ {
+ int32 result;
+ __atomic_load(dst, &result, __ATOMIC_SEQ_CST);
+ return result;
+ }
+ FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
+ {
+ int64 result;
+ __atomic_load(dst, &result, __ATOMIC_SEQ_CST);
+ return result;
+ }
+ FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value)
+ {
+ __atomic_store(dst, &value, __ATOMIC_SEQ_CST);
+ }
+ FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value)
+ {
+ __atomic_store(dst, &value, __ATOMIC_SEQ_CST);
+ }
+ FORCE_INLINE static void Prefetch(void const* ptr)
+ {
+ __builtin_prefetch(static_cast(ptr));
+ }
+ static bool Is64BitPlatform();
+ static CPUInfo GetCPUInfo();
+ static int32 GetCacheLineSize();
+ static MemoryStats GetMemoryStats();
+ static ProcessMemoryStats GetProcessMemoryStats();
+ static uint64 GetCurrentThreadID();
+ static void SetThreadPriority(ThreadPriority priority);
+ static void SetThreadAffinityMask(uint64 affinityMask);
+ static void Sleep(int32 milliseconds);
+ static double GetTimeSeconds();
+ static uint64 GetTimeCycles();
+ static uint64 GetClockFrequency();
+ static void GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
+ static void GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond);
+ static bool Init();
+ static void BeforeRun();
+ static void Tick();
+ static void BeforeExit();
+ static void Exit();
+ static int32 GetDpi();
+ static String GetUserLocaleName();
+ static String GetComputerName();
+ static bool GetHasFocus();
+ static bool CanOpenUrl(const StringView& url);
+ static void OpenUrl(const StringView& url);
+ static Vector2 GetMousePosition();
+ static void SetMousePosition(const Vector2& pos);
+ static Rectangle GetMonitorBounds(const Vector2& screenPos);
+ static Vector2 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 void* LoadLibrary(const Char* filename);
+ static void FreeLibrary(void* handle);
+ static void* GetProcAddress(void* handle, const char* symbol);
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacPlatformSettings.h b/Source/Engine/Platform/Mac/MacPlatformSettings.h
new file mode 100644
index 000000000..c5030fc58
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacPlatformSettings.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC || USE_EDITOR
+
+#include "Engine/Core/Config/PlatformSettingsBase.h"
+
+///
+/// Mac platform settings.
+///
+API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API MacPlatformSettings : public SettingsBase
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(MacPlatformSettings);
+
+ ///
+ /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
+ ///
+ static MacPlatformSettings* Get();
+
+ // [SettingsBase]
+ void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override
+ {
+ }
+};
+
+#if PLATFORM_MAC
+typedef MacPlatformSettings PlatformSettings;
+#endif
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacStringUtils.cpp b/Source/Engine/Platform/Mac/MacStringUtils.cpp
new file mode 100644
index 000000000..f84026c46
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacStringUtils.cpp
@@ -0,0 +1,277 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "Engine/Platform/StringUtils.h"
+#include
+#include
+#include
+#include
+#include
+
+bool StringUtils::IsUpper(char c)
+{
+ return isupper(c) != 0;
+}
+
+bool StringUtils::IsLower(char c)
+{
+ return islower(c) != 0;
+}
+
+bool StringUtils::IsAlpha(char c)
+{
+ return iswalpha(c) != 0;
+}
+
+bool StringUtils::IsPunct(char c)
+{
+ return ispunct(c) != 0;
+}
+
+bool StringUtils::IsAlnum(char c)
+{
+ return isalnum(c) != 0;
+}
+
+bool StringUtils::IsDigit(char c)
+{
+ return isdigit(c) != 0;
+}
+
+bool StringUtils::IsHexDigit(char c)
+{
+ return isxdigit(c) != 0;
+}
+
+bool StringUtils::IsWhitespace(char c)
+{
+ return isspace(c) != 0;
+}
+
+char StringUtils::ToUpper(char c)
+{
+ return toupper(c);
+}
+
+char StringUtils::ToLower(char c)
+{
+ return tolower(c);
+}
+
+bool StringUtils::IsUpper(Char c)
+{
+ return iswupper(c) != 0;
+}
+
+bool StringUtils::IsLower(Char c)
+{
+ return iswlower(c) != 0;
+}
+
+bool StringUtils::IsAlpha(Char c)
+{
+ return iswalpha(c) != 0;
+}
+
+bool StringUtils::IsPunct(Char c)
+{
+ return iswpunct(c) != 0;
+}
+
+bool StringUtils::IsAlnum(Char c)
+{
+ return iswalnum(c) != 0;
+}
+
+bool StringUtils::IsDigit(Char c)
+{
+ return iswdigit(c) != 0;
+}
+
+bool StringUtils::IsHexDigit(Char c)
+{
+ return iswxdigit(c) != 0;
+}
+
+bool StringUtils::IsWhitespace(Char c)
+{
+ return iswspace(c) != 0;
+}
+
+Char StringUtils::ToUpper(Char c)
+{
+ return towupper(c);
+}
+
+Char StringUtils::ToLower(Char c)
+{
+ return towlower(c);
+}
+
+int32 StringUtils::Compare(const Char* str1, const Char* str2)
+{
+ Char c1, c2;
+ int32 i;
+ do
+ {
+ c1 = *str1++;
+ c2 = *str2++;
+ i = (int32)c1 - (int32)c2;
+ } while (i == 0 && c1 && c2);
+ return i;
+}
+
+int32 StringUtils::Compare(const Char* str1, const Char* str2, int32 maxCount)
+{
+ Char c1, c2;
+ int32 i;
+ if (maxCount == 0)
+ return 0;
+ do
+ {
+ c1 = *str1++;
+ c2 = *str2++;
+ i = (int32)c1 - (int32)c2;
+ maxCount--;
+ } while (i == 0 && c1 && c2 && maxCount);
+ return i;
+}
+
+int32 StringUtils::CompareIgnoreCase(const Char* str1, const Char* str2)
+{
+ Char c1, c2;
+ int32 i;
+ do
+ {
+ c1 = ToLower(*str1++);
+ c2 = ToLower(*str2++);
+ i = (int32)c1 - (int32)c2;
+ } while (i == 0 && c1 && c2);
+ return i;
+}
+
+int32 StringUtils::CompareIgnoreCase(const Char* str1, const Char* str2, int32 maxCount)
+{
+ Char c1, c2;
+ int32 i;
+ if (maxCount == 0)
+ return 0;
+ do
+ {
+ c1 = ToLower(*str1++);
+ c2 = ToLower(*str2++);
+ i = (int32)c1 - (int32)c2;
+ maxCount--;
+ } while (i == 0 && c1 && c2 && maxCount);
+ return i;
+}
+
+int32 StringUtils::Length(const Char* str)
+{
+ if (!str)
+ return 0;
+ const Char* ptr = str;
+ for (; *ptr; ++ptr)
+ {
+ }
+ return ptr - str;
+}
+
+int32 StringUtils::Length(const char* str)
+{
+ if (!str)
+ return 0;
+ return static_cast(strlen(str));
+}
+
+int32 StringUtils::Compare(const char* str1, const char* str2)
+{
+ return strcmp(str1, str2);
+}
+
+int32 StringUtils::Compare(const char* str1, const char* str2, int32 maxCount)
+{
+ return strncmp(str1, str2, maxCount);
+}
+
+int32 StringUtils::CompareIgnoreCase(const char* str1, const char* str2)
+{
+ return strcasecmp(str1, str2);
+}
+
+int32 StringUtils::CompareIgnoreCase(const char* str1, const char* str2, int32 maxCount)
+{
+ return strncasecmp(str1, str2, maxCount);
+}
+
+Char* StringUtils::Copy(Char* dst, const Char* src)
+{
+ Char* q = dst;
+ const Char* p = src;
+ Char ch;
+ do
+ {
+ *q++ = ch = *p++;
+ } while (ch);
+ return dst;
+}
+
+Char* StringUtils::Copy(Char* dst, const Char* src, int32 count)
+{
+ Char* q = dst;
+ const Char* p = src;
+ char ch;
+ while (count)
+ {
+ count--;
+ *q++ = ch = *p++;
+ if (!ch)
+ break;
+ }
+ *q = 0;
+ return dst;
+}
+
+const Char* StringUtils::Find(const Char* str, const Char* toFind)
+{
+ while (*str)
+ {
+ const Char* start = str;
+ const Char* sub = toFind;
+
+ // If first character of sub string match, check for whole string
+ while (*str && *sub && *str == *sub)
+ {
+ str++;
+ sub++;
+ }
+
+ // If complete substring match, return starting address
+ if (!*sub)
+ return (Char*)start;
+
+ // Increment main string
+ str = start + 1;
+ }
+
+ // No matches
+ return nullptr;
+}
+
+const char* StringUtils::Find(const char* str, const char* toFind)
+{
+ return strstr(str, toFind);
+}
+
+void StringUtils::ConvertANSI2UTF16(const char* from, Char* to, int32 len)
+{
+ todo;
+}
+
+void StringUtils::ConvertUTF162ANSI(const Char* from, char* to, int32 len)
+{
+ todo;
+}
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacThread.cpp b/Source/Engine/Platform/Mac/MacThread.cpp
new file mode 100644
index 000000000..dba77dda6
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacThread.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "MacThread.h"
+#include "Engine/Threading/IRunnable.h"
+#include "Engine/Threading/ThreadRegistry.h"
+
+MacThread::MacThread(IRunnable* runnable, const String& name, ThreadPriority priority)
+ : ThreadBase(runnable, name, priority)
+{
+}
+
+MacThread::~MacThread()
+{
+}
+
+MacThread* Create(IRunnable* runnable, const String& name, ThreadPriority priority = ThreadPriority::Normal, uint32 stackSize = 0)
+{
+ // TODO: imp this
+}
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacThread.h b/Source/Engine/Platform/Mac/MacThread.h
new file mode 100644
index 000000000..d30ba696b
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacThread.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "../Base/ThreadBase.h"
+
+///
+/// Thread object for Mac platform.
+///
+class MacThread : public ThreadBase
+{
+public:
+
+ MacThread(IRunnable* runnable, const String& name, ThreadPriority priority);
+ ~MacThread();
+ static MacThread* Create(IRunnable* runnable, const String& name, ThreadPriority priority = ThreadPriority::Normal, uint32 stackSize = 0);
+
+ // [ThreadBase]
+ void Join() override;
+
+protected:
+
+ // [ThreadBase]
+ void ClearHandleInternal();
+ void SetPriorityInternal(ThreadPriority priority);
+ void KillInternal(bool waitForJoin);
+};
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacWindow.cpp b/Source/Engine/Platform/Mac/MacWindow.cpp
new file mode 100644
index 000000000..7afa8176e
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacWindow.cpp
@@ -0,0 +1,103 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#if PLATFORM_MAC
+
+#include "../Window.h"
+
+MacWindow::MacWindow(const CreateWindowSettings& settings)
+ : WindowBase(settings)
+{
+ int32 x = Math::TruncToInt(settings.Position.X);
+ int32 y = Math::TruncToInt(settings.Position.Y);
+ int32 clientWidth = Math::TruncToInt(settings.Size.X);
+ int32 clientHeight = Math::TruncToInt(settings.Size.Y);
+ int32 windowWidth = clientWidth;
+ int32 windowHeight = clientHeight;
+ _clientSize = Vector2((float)clientWidth, (float)clientHeight);
+
+ // TODO: setup window
+}
+
+MacWindow::~MacWindow()
+{
+ // TODO: close window
+}
+
+void* MacWindow::GetNativePtr() const
+{
+ // TODO: return window handle
+ return nullptr;
+}
+
+void MacWindow::Show()
+{
+ if (!_visible)
+ {
+ InitSwapChain();
+ if (_showAfterFirstPaint)
+ {
+ if (RenderTask)
+ RenderTask->Enabled = true;
+ return;
+ }
+
+ // Show
+ // TODO: show window
+ _focused = true;
+
+ // Base
+ WindowBase::Show();
+ }
+}
+
+void MacWindow::Hide()
+{
+ if (_visible)
+ {
+ // Hide
+ // TODO: hide window
+
+ // Base
+ WindowBase::Hide();
+ }
+}
+
+void MacWindow::Minimize()
+{
+ // TODO: Minimize
+}
+
+void MacWindow::Maximize()
+{
+ // TODO: Maximize
+}
+
+void MacWindow::Restore()
+{
+ // TODO: Restore
+}
+
+bool MacWindow::IsClosed() const
+{
+ return false;
+}
+
+bool MacWindow::IsForegroundWindow() const
+{
+ return Platform::GetHasFocus();
+}
+
+void MacWindow::SetIsFullscreen(bool isFullscreen)
+{
+}
+
+void MacWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& height) const
+{
+ // TODO: proper screen info
+ x = 0;
+ y = 0;
+ width = (int32)_clientSize.X;
+ height = (int32)_clientSize.Y;
+}
+
+#endif
diff --git a/Source/Engine/Platform/Mac/MacWindow.h b/Source/Engine/Platform/Mac/MacWindow.h
new file mode 100644
index 000000000..1bdd1ff55
--- /dev/null
+++ b/Source/Engine/Platform/Mac/MacWindow.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if PLATFORM_MAC
+
+#include "Engine/Platform/Base/WindowBase.h"
+#include "Engine/Platform/Platform.h"
+
+///
+/// Implementation of the window class for Mac platform.
+///
+class MacWindow : public WindowBase
+{
+private:
+
+ Vector2 _clientSize;
+
+public:
+
+ MacWindow(const CreateWindowSettings& settings);
+ ~MacWindow();
+
+public:
+
+ // [Window]
+ void* GetNativePtr() const override;
+ void Show() override;
+ void Hide() override;
+ void Minimize() override;
+ void Maximize() override;
+ void Restore() override;
+ bool IsClosed() const override;
+ bool IsForegroundWindow() const override;
+ void SetIsFullscreen(bool isFullscreen) override;
+};
+
+#endif
diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h
index 1e04b185d..03435c4e0 100644
--- a/Source/Engine/Platform/Network.h
+++ b/Source/Engine/Platform/Network.h
@@ -2,24 +2,18 @@
#pragma once
-#if PLATFORM_WINDOWS
+#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32Network.h"
-#elif PLATFORM_UWP
-#include "Win32/Win32Network.h"
-#elif PLATFORM_LINUX
+#elif PLATFORM_LINUX || PLATFORM_ANDROID
#include "Unix/UnixNetwork.h"
#elif PLATFORM_PS4
#include "Platforms/PS4/Engine/Platform/PS4Network.h"
#elif PLATFORM_PS5
#include "Platforms/PS5/Engine/Platform/PS5Network.h"
-#elif PLATFORM_XBOX_ONE
-#include "Win32/Win32Network.h"
-#elif PLATFORM_XBOX_SCARLETT
-#include "Win32/Win32Network.h"
-#elif PLATFORM_ANDROID
-#include "Unix/UnixNetwork.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchNetwork.h"
+#elif PLATFORM_MAC
+#include "Mac/MacNetwork.h"
#else
#error Missing Network implementation!
#endif
diff --git a/Source/Engine/Platform/Platform.Build.cs b/Source/Engine/Platform/Platform.Build.cs
index b8f006a25..05f6078b5 100644
--- a/Source/Engine/Platform/Platform.Build.cs
+++ b/Source/Engine/Platform/Platform.Build.cs
@@ -77,6 +77,9 @@ public class Platform : EngineModule
case TargetPlatform.Switch:
options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "Platform"));
break;
+ case TargetPlatform.Mac:
+ options.SourcePaths.Add(Path.Combine(FolderPath, "Mac"));
+ break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
if (options.Target.IsEditor)
@@ -87,6 +90,7 @@ public class Platform : EngineModule
options.SourceFiles.Add(Path.Combine(FolderPath, "Linux", "LinuxPlatformSettings.h"));
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"));
AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxOne", "Engine", "Platform", "XboxOnePlatformSettings.h"));
AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "XboxScarlett", "Engine", "Platform", "XboxScarlettPlatformSettings.h"));
AddSourceFileIfExists(options, Path.Combine(Globals.EngineRoot, "Source", "Platforms", "PS4", "Engine", "Platform", "PS4PlatformSettings.h"));
diff --git a/Source/Engine/Platform/Platform.h b/Source/Engine/Platform/Platform.h
index 0b61945e5..5c07844b3 100644
--- a/Source/Engine/Platform/Platform.h
+++ b/Source/Engine/Platform/Platform.h
@@ -26,6 +26,8 @@
#include "Android/AndroidPlatform.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchPlatform.h"
+#elif PLATFORM_MAC
+#include "Mac/MacPlatform.h"
#else
#error Missing Platform implementation!
#endif
diff --git a/Source/Engine/Platform/Thread.h b/Source/Engine/Platform/Thread.h
index feb242ebb..b9a4f8103 100644
--- a/Source/Engine/Platform/Thread.h
+++ b/Source/Engine/Platform/Thread.h
@@ -2,9 +2,7 @@
#pragma once
-#if PLATFORM_WINDOWS
-#include "Win32/Win32Thread.h"
-#elif PLATFORM_UWP
+#if PLATFORM_WINDOWS || PLATFORM_UWP || PLATFORM_XBOX_ONE || PLATFORM_XBOX_SCARLETT
#include "Win32/Win32Thread.h"
#elif PLATFORM_LINUX
#include "Linux/LinuxThread.h"
@@ -12,14 +10,12 @@
#include "Platforms/PS4/Engine/Platform/PS4Thread.h"
#elif PLATFORM_PS5
#include "Platforms/PS5/Engine/Platform/PS5Thread.h"
-#elif PLATFORM_XBOX_ONE
-#include "Win32/Win32Thread.h"
-#elif PLATFORM_XBOX_SCARLETT
-#include "Win32/Win32Thread.h"
#elif PLATFORM_ANDROID
#include "Android/AndroidThread.h"
#elif PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchThread.h"
+#elif PLATFORM_MAC
+#include "Mac/MacThread.h"
#else
#error Missing Thread implementation!
#endif
diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h
index 3529dcf47..e82bdac71 100644
--- a/Source/Engine/Platform/Types.h
+++ b/Source/Engine/Platform/Types.h
@@ -147,8 +147,8 @@ class Win32Thread;
typedef Win32Thread Thread;
class GDKWindow;
typedef GDKWindow Window;
-class NetworkBase;
-typedef NetworkBase Network;
+class Win32Network;
+typedef Win32Network Network;
class GDKUser;
typedef GDKUser User;
@@ -172,8 +172,8 @@ class Win32Thread;
typedef Win32Thread Thread;
class GDKWindow;
typedef GDKWindow Window;
-class NetworkBase;
-typedef NetworkBase Network;
+class Win32Network;
+typedef Win32Network Network;
class GDKUser;
typedef GDKUser User;
@@ -227,6 +227,31 @@ typedef SwitchNetwork Network;
class UserBase;
typedef UserBase User;
+#elif PLATFORM_MAC
+
+class ClipboardBase;
+typedef ClipboardBase Clipboard;
+class MacCriticalSection;
+typedef MacCriticalSection CriticalSection;
+class MacConditionVariable;
+typedef MacConditionVariable ConditionVariable;
+class MacFileSystem;
+typedef MacFileSystem FileSystem;
+class FileSystemWatcherBase;
+typedef FileSystemWatcherBase FileSystemWatcher;
+class MacFile;
+typedef MacFile File;
+class MacPlatform;
+typedef MacPlatform Platform;
+class MacThread;
+typedef MacThread Thread;
+class MacWindow;
+typedef MacWindow Window;
+class MacNetwork;
+typedef MacNetwork Network;
+class UserBase;
+typedef UserBase User;
+
#else
#error Missing Types implementation!
diff --git a/Source/Engine/Platform/Window.h b/Source/Engine/Platform/Window.h
index ea2bb6e8e..5e1797727 100644
--- a/Source/Engine/Platform/Window.h
+++ b/Source/Engine/Platform/Window.h
@@ -20,6 +20,8 @@
#include "Platforms/Switch/Engine/Platform/SwitchWindow.h"
#elif PLATFORM_PS5
#include "Platforms/PS5/Engine/Platform/PS5Window.h"
+#elif PLATFORM_MAC
+#include "Mac/MacWindow.h"
#else
#error Missing Window implementation!
#endif
diff --git a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
index d1acb0b4b..bbc2cb8df 100644
--- a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
+++ b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs
@@ -35,6 +35,7 @@ public class TextureTool : EngineModule
case TargetPlatform.PS5:
case TargetPlatform.Android:
case TargetPlatform.Switch:
+ case TargetPlatform.Mac:
useStb = true;
break;
default: throw new InvalidPlatformException(options.Platform.Target);
diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp
index e572f4db2..b2a1c1b9a 100644
--- a/Source/Engine/Visject/ShaderGraph.cpp
+++ b/Source/Engine/Visject/ShaderGraph.cpp
@@ -769,6 +769,7 @@ void ShaderGenerator::ProcessGroupTools(Box* box, Node* node, Value& value)
PLATFORM_CASE(8, "PLATFORM_ANDROID");
PLATFORM_CASE(9, "PLATFORM_SWITCH");
PLATFORM_CASE(10, "PLATFORM_PS5");
+ PLATFORM_CASE(11, "PLATFORM_MAC");
#undef PLATFORM_CASE
break;
}
diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp
index 9e98fa5f5..e3ab770b5 100644
--- a/Source/Engine/Visject/VisjectGraph.cpp
+++ b/Source/Engine/Visject/VisjectGraph.cpp
@@ -917,6 +917,9 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
case PlatformType::PS5:
boxId = 10;
break;
+ case PlatformType::Mac:
+ boxId = 11;
+ break;
default: ;
}
value = tryGetValue(node->GetBox(node->GetBox(boxId)->HasConnection() ? boxId : 1), Value::Zero);
diff --git a/Source/Tools/Flax.Build/Build/Module.cs b/Source/Tools/Flax.Build/Build/Module.cs
index 942cc8e2b..bf1c6df43 100644
--- a/Source/Tools/Flax.Build/Build/Module.cs
+++ b/Source/Tools/Flax.Build/Build/Module.cs
@@ -97,6 +97,7 @@ namespace Flax.Build
case TargetPlatform.XboxScarlett: return "PLATFORM_XBOX_SCARLETT";
case TargetPlatform.Android: return "PLATFORM_ANDROID";
case TargetPlatform.Switch: return "PLATFORM_SWITCH";
+ case TargetPlatform.Mac: return "PLATFORM_MAC";
default: throw new InvalidPlatformException(platform);
}
}
diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs
index ebc4e9023..a2c875eb0 100644
--- a/Source/Tools/Flax.Build/Build/Platform.cs
+++ b/Source/Tools/Flax.Build/Build/Platform.cs
@@ -34,6 +34,7 @@ namespace Flax.Build
case PlatformID.Win32Windows:
case PlatformID.WinCE: return TargetPlatform.Windows;
case PlatformID.Unix: return TargetPlatform.Linux;
+ case PlatformID.MacOSX: return TargetPlatform.Mac;
default: throw new NotImplementedException(string.Format("Unsupported build platform {0}.", platformId));
}
}
@@ -242,6 +243,7 @@ namespace Flax.Build
case TargetPlatform.PS5: return targetArchitecture == TargetArchitecture.x64;
case TargetPlatform.Android: return targetArchitecture == TargetArchitecture.ARM64;
case TargetPlatform.Switch: return targetArchitecture == TargetArchitecture.ARM64;
+ case TargetPlatform.Mac: return targetArchitecture == TargetArchitecture.x64;
default: return false;
}
}
diff --git a/Source/Tools/Flax.Build/Build/TargetPlatform.cs b/Source/Tools/Flax.Build/Build/TargetPlatform.cs
index 81b84b6e9..da850ed20 100644
--- a/Source/Tools/Flax.Build/Build/TargetPlatform.cs
+++ b/Source/Tools/Flax.Build/Build/TargetPlatform.cs
@@ -51,6 +51,11 @@ namespace Flax.Build
/// Running on PlayStation 5.
///
PS5 = 9,
+
+ ///
+ /// Running on Mac.
+ ///
+ Mac = 10,
}
///
diff --git a/Source/Tools/Flax.Build/Deps/Dependency.cs b/Source/Tools/Flax.Build/Deps/Dependency.cs
index 0a873334e..3ace8dd8d 100644
--- a/Source/Tools/Flax.Build/Deps/Dependency.cs
+++ b/Source/Tools/Flax.Build/Deps/Dependency.cs
@@ -239,6 +239,7 @@ namespace Flax.Deps
case TargetPlatform.Linux:
case TargetPlatform.PS4:
case TargetPlatform.PS5:
+ case TargetPlatform.Mac:
{
cmdLine = "CMakeLists.txt";
break;
diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj
index abf2bfd86..fdfce10d2 100644
--- a/Source/Tools/Flax.Build/Flax.Build.csproj
+++ b/Source/Tools/Flax.Build/Flax.Build.csproj
@@ -139,6 +139,7 @@
+
@@ -160,6 +161,7 @@
+
diff --git a/Source/Tools/Flax.Build/Globals.cs b/Source/Tools/Flax.Build/Globals.cs
index a9a29af88..61d2382d3 100644
--- a/Source/Tools/Flax.Build/Globals.cs
+++ b/Source/Tools/Flax.Build/Globals.cs
@@ -36,6 +36,7 @@ namespace Flax.Build
TargetPlatform.XboxScarlett,
TargetPlatform.Android,
TargetPlatform.Switch,
+ TargetPlatform.Mac,
};
///
diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
new file mode 100644
index 000000000..ab21dc54b
--- /dev/null
+++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs
@@ -0,0 +1,60 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using Flax.Build.Projects;
+
+namespace Flax.Build.Platforms
+{
+ ///
+ /// The build platform for all Mac systems.
+ ///
+ ///
+ public sealed class MacPlatform : Platform
+ {
+ ///
+ public override TargetPlatform Target => TargetPlatform.Mac;
+
+ ///
+ public override bool HasRequiredSDKsInstalled { get; }
+
+ ///
+ public override bool HasSharedLibrarySupport => true;
+
+ ///
+ public override string ExecutableFileExtension => string.Empty;
+
+ ///
+ public override string SharedLibraryFileExtension => ".dylib";
+
+ ///
+ public override string StaticLibraryFileExtension => ".a";
+
+ ///
+ public override string ProgramDatabaseFileExtension => ".dSYM";
+
+ ///
+ public override string SharedLibraryFilePrefix => string.Empty;
+
+ ///
+ public override string StaticLibraryFilePrefix => "lib";
+
+ ///
+ public override ProjectFormat DefaultProjectFormat => ProjectFormat.XCode;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MacPlatform()
+ {
+ if (Platform.BuildTargetPlatform != TargetPlatform.Mac)
+ return;
+
+ throw new System.NotImplementedException("TODO: detect MacSDK installation");
+ }
+
+ ///
+ protected override Toolchain CreateToolchain(TargetArchitecture architecture)
+ {
+ return new MacToolchain(this, architecture);
+ }
+ }
+}
diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs
new file mode 100644
index 000000000..1d1e88d3e
--- /dev/null
+++ b/Source/Tools/Flax.Build/Platforms/Mac/MacToolchain.cs
@@ -0,0 +1,58 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using Flax.Build.Graph;
+using Flax.Build.NativeCpp;
+
+namespace Flax.Build.Platforms
+{
+ ///
+ /// The build toolchain for all Mac systems.
+ ///
+ ///
+ public sealed class MacToolchain : Toolchain
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The platform.
+ /// The target architecture.
+ public MacToolchain(MacPlatform platform, TargetArchitecture architecture)
+ : base(platform, architecture)
+ {
+ }
+
+ ///
+ public override string DllExport => "__attribute__((__visibility__(\\\"default\\\")))";
+
+ ///
+ public override string DllImport => "";
+
+ ///
+ public override void LogInfo()
+ {
+ throw new NotImplementedException("TODO: MacToolchain.LogInfo");
+ }
+
+ ///
+ public override void SetupEnvironment(BuildOptions options)
+ {
+ base.SetupEnvironment(options);
+
+ options.CompileEnv.PreprocessorDefinitions.Add("PLATFORM_MAC");
+ }
+
+ ///
+ public override CompileOutput CompileCppFiles(TaskGraph graph, BuildOptions options, List sourceFiles, string outputPath)
+ {
+ throw new NotImplementedException("TODO: MacToolchain.CompileCppFiles");
+ }
+
+ ///
+ public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath)
+ {
+ throw new NotImplementedException("TODO: MacToolchain.LinkFiles");
+ }
+ }
+}
diff --git a/Source/Tools/Flax.Build/Projects/ProjectFormat.cs b/Source/Tools/Flax.Build/Projects/ProjectFormat.cs
index 4014ddfa9..6ab86b2d5 100644
--- a/Source/Tools/Flax.Build/Projects/ProjectFormat.cs
+++ b/Source/Tools/Flax.Build/Projects/ProjectFormat.cs
@@ -41,5 +41,10 @@ namespace Flax.Build.Projects
/// Visual Studio Code.
///
VisualStudioCode,
+
+ ///
+ /// XCode.
+ ///
+ XCode,
}
}
diff --git a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs
index 820e0d020..f218a9a4f 100644
--- a/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs
+++ b/Source/Tools/Flax.Build/Projects/ProjectGenerator.cs
@@ -139,6 +139,7 @@ namespace Flax.Build.Projects
}
}
case ProjectFormat.VisualStudioCode: return new VisualStudioCodeProjectGenerator();
+ case ProjectFormat.XCode: return new XCodeProjectGenerator();
case ProjectFormat.Custom:
if (CustomProjectTypes.TryGetValue(Configuration.ProjectFormatCustom, out var factory))
return factory(type);
diff --git a/Source/Tools/Flax.Build/Projects/XCode.cs b/Source/Tools/Flax.Build/Projects/XCode.cs
new file mode 100644
index 000000000..56fdd894e
--- /dev/null
+++ b/Source/Tools/Flax.Build/Projects/XCode.cs
@@ -0,0 +1,49 @@
+// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
+
+using System;
+
+namespace Flax.Build.Projects
+{
+ ///
+ /// Project generator for XCode.
+ ///
+ public class XCodeProjectGenerator : ProjectGenerator
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public XCodeProjectGenerator()
+ {
+ }
+
+ ///
+ public override string ProjectFileExtension => "xcodeproj";
+
+ ///
+ public override string SolutionFileExtension => string.Empty;
+
+ ///
+ public override TargetType? Type => null;
+
+ ///
+ public override Project CreateProject()
+ {
+ return new Project
+ {
+ Generator = this,
+ };
+ }
+
+ ///
+ public override void GenerateProject(Project project)
+ {
+ throw new NotImplementedException("TODO: XCode");
+ }
+
+ ///
+ public override void GenerateSolution(Solution solution)
+ {
+ throw new NotImplementedException("TODO: XCode");
+ }
+ }
+}