Files
FlaxEngine/Source/Engine/Platform/Apple/ApplePlatform.cpp
Andrew Spiering f40657ea04 macOS support fixes
* Adding macOS FileSystemWatcher, this should allow files to be monitored and update like the other OSs
* Reworked how macOS launches processes to use NSTask which just deals with escaped and unescaped paths better
* Made a change to the ScriptsBuilder::RunBuildTool, this was adding the escaped values to the path, in reality it should be up to the underlying OS to make sure things are properly escaped, so removed those as they just end up causing issues. Also instead of appending the args to the fileName we just properly use the Arguments variable on the CreateProcessSettings
* No longer use open in order to show files in the finder, we use the proper method selectFile
* made a slight cleanup change to the MacPlatform Tick function
* Added ToNSString functions just to make that easier
* Added a ParseArguments function that will take a string and turn it into an array for NSTask
2023-09-19 20:58:12 -07:00

464 lines
13 KiB
C++

// 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 <Foundation/Foundation.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;
NSAutoreleasePool* AutoreleasePool = nullptr;
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);
}
NSString* AppleUtils::ToNSString(const StringView& str)
{
NSString* ret = !str.IsEmpty() ? [[NSString alloc] initWithBytes: (const UInt8*)str.Get() length: str.Length() * sizeof(Char) encoding: NSUTF16LittleEndianStringEncoding] : nil;
return ret ? ret : @"";
}
NSString* AppleUtils::ToNSString(const char* string)
{
NSString* ret = string ? [NSString stringWithUTF8String: string] : nil;
return ret ? ret : @"";
}
NSArray* AppleUtils::ParseArguments(NSString* argsString) {
NSMutableArray *argsArray = [NSMutableArray array];
NSScanner *scanner = [NSScanner scannerWithString:argsString];
NSString *currentArg = nil;
BOOL insideQuotes = NO;
while (![scanner isAtEnd]) {
if (insideQuotes) {
[scanner scanUpToString:@"\"" intoString:&currentArg];
[scanner scanString:@"\"" intoString:NULL];
insideQuotes = NO;
} else {
[scanner scanUpToString:@" " intoString:&currentArg];
[scanner scanString:@" " intoString:NULL];
}
if ([currentArg isEqualToString:@"\""]) {
insideQuotes = YES;
} else if (currentArg) {
[argsArray addObject:currentArg];
}
}
return [argsArray copy];
}
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;
}
void ApplePlatform::Log(const StringView& msg)
{
#if !BUILD_RELEASE && !USE_EDITOR
NSLog(@"%s", StringAsANSI<>(*msg, msg.Length()).Get());
#endif
}
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));
}
AutoreleasePool = [[NSAutoreleasePool alloc] init];
return false;
}
void ApplePlatform::Tick()
{
// TODO: do it once every X fames
[AutoreleasePool drain];
AutoreleasePool = [[NSAutoreleasePool alloc] init];
}
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];
}
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