csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
8.1 KiB
312 lines
8.1 KiB
#include "common.h"
|
|
|
|
class PlatformThreadingInterface;
|
|
|
|
|
|
class LoopCancellation : public ComSingleObject<IAvnLoopCancellation, &IID_IAvnLoopCancellation>
|
|
{
|
|
public:
|
|
FORWARD_IUNKNOWN()
|
|
|
|
bool Running = false;
|
|
bool Cancelled = false;
|
|
bool IsApp = false;
|
|
|
|
virtual void Cancel() override
|
|
{
|
|
Cancelled = true;
|
|
if(Running)
|
|
{
|
|
if(![NSThread isMainThread])
|
|
{
|
|
AddRef();
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if(Release() == 0)
|
|
return;
|
|
Cancel();
|
|
});
|
|
return;
|
|
};
|
|
|
|
Running = false;
|
|
if(IsApp)
|
|
[NSApp stop:nil];
|
|
|
|
// Wakeup the event loop
|
|
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
|
location:NSMakePoint(0, 0)
|
|
modifierFlags:0
|
|
timestamp:0
|
|
windowNumber:0
|
|
context:nil
|
|
subtype:0
|
|
data1:0
|
|
data2:0];
|
|
[NSApp postEvent:event atStart:YES];
|
|
}
|
|
};
|
|
};
|
|
|
|
// CFRunLoopTimerSetNextFireDate docs recommend to "create a repeating timer with an initial
|
|
// firing time in the distant future (or the initial firing time) and a very large repeat
|
|
// interval—on the order of decades or more"
|
|
static double distantFutureInterval = (double)50*365*24*3600;
|
|
|
|
|
|
|
|
@implementation ObserverStateHolder : NSObject
|
|
{
|
|
@public bool InsideCallback;
|
|
}
|
|
@end
|
|
|
|
@implementation ObserverHolder : NSObject
|
|
{
|
|
@public CFRunLoopObserverRef Observer;
|
|
@public ObserverStateHolder* State;
|
|
}
|
|
|
|
- (ObserverHolder*) init
|
|
{
|
|
self = [super init];
|
|
self->State = [ObserverStateHolder new];
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
if(Observer != nil)
|
|
{
|
|
CFRunLoopObserverInvalidate(Observer);
|
|
CFRelease(Observer);
|
|
Observer = nil;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@interface Signaler : NSObject
|
|
-(void) setEvents:(IAvnPlatformThreadingInterfaceEvents*) events;
|
|
-(void) updateTimer:(int)ms;
|
|
-(Signaler*) init;
|
|
-(void) destroyObserver;
|
|
-(void) signal;
|
|
@end
|
|
|
|
@implementation Signaler
|
|
{
|
|
ComPtr<IAvnPlatformThreadingInterfaceEvents> _events;
|
|
bool _wakeupDelegateSent;
|
|
bool _signaled;
|
|
bool _backgroundProcessingRequested;
|
|
@public ObserverHolder* Observer;
|
|
CFRunLoopTimerRef _timer;
|
|
}
|
|
|
|
- (void) checkSignaled
|
|
{
|
|
bool signaled;
|
|
@synchronized (self) {
|
|
signaled = _signaled;
|
|
_signaled = false;
|
|
}
|
|
if(signaled)
|
|
{
|
|
_events->Signaled();
|
|
}
|
|
}
|
|
|
|
- (ObserverHolder*) createObserver
|
|
{
|
|
ObserverHolder* holder = [ObserverHolder new];
|
|
ObserverStateHolder* state = holder->State;
|
|
|
|
holder->Observer = CFRunLoopObserverCreateWithHandler(nil,
|
|
kCFRunLoopBeforeSources
|
|
| kCFRunLoopAfterWaiting
|
|
| kCFRunLoopBeforeWaiting
|
|
,
|
|
true, 0,
|
|
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
|
|
state->InsideCallback = true;
|
|
if(activity == kCFRunLoopBeforeWaiting)
|
|
{
|
|
bool triggerProcessing;
|
|
@synchronized (self) {
|
|
triggerProcessing = self->_backgroundProcessingRequested;
|
|
self->_backgroundProcessingRequested = false;
|
|
}
|
|
if(triggerProcessing)
|
|
self->_events->ReadyForBackgroundProcessing();
|
|
}
|
|
[self checkSignaled];
|
|
state->InsideCallback = false;
|
|
});
|
|
CFRunLoopAddObserver(CFRunLoopGetMain(), holder->Observer, kCFRunLoopCommonModes);
|
|
return holder;
|
|
}
|
|
|
|
- (Signaler*) init
|
|
{
|
|
Observer = [self createObserver];
|
|
_timer = CFRunLoopTimerCreateWithHandler(nil, CFAbsoluteTimeGetCurrent() + distantFutureInterval, distantFutureInterval, 0, 0, ^(CFRunLoopTimerRef timer) {
|
|
self->_events->Timer();
|
|
});
|
|
|
|
CFRunLoopAddTimer(CFRunLoopGetMain(), _timer, kCFRunLoopCommonModes);
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) destroyObserver
|
|
{
|
|
Observer = nil;
|
|
|
|
if(_timer != nil)
|
|
{
|
|
CFRunLoopTimerInvalidate(_timer);
|
|
CFRelease(_timer);
|
|
_timer = nil;
|
|
}
|
|
}
|
|
|
|
-(void) updateTimer:(int)ms
|
|
{
|
|
if(_timer == nil)
|
|
return;
|
|
double interval = ms < 0 ? distantFutureInterval : ((double)ms / 1000);
|
|
CFRunLoopTimerSetTolerance(_timer, 0);
|
|
CFRunLoopTimerSetNextFireDate(_timer, CFAbsoluteTimeGetCurrent() + interval);
|
|
}
|
|
|
|
- (void) setEvents: (IAvnPlatformThreadingInterfaceEvents*) events
|
|
{
|
|
_events = events;
|
|
}
|
|
|
|
- (void) signal
|
|
{
|
|
@synchronized (self) {
|
|
if(_signaled)
|
|
return;
|
|
_signaled = true;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self checkSignaled];
|
|
});
|
|
CFRunLoopWakeUp(CFRunLoopGetMain());
|
|
}
|
|
}
|
|
|
|
- (void) requestBackgroundProcessing
|
|
{
|
|
@synchronized (self) {
|
|
if(_backgroundProcessingRequested)
|
|
return;
|
|
_backgroundProcessingRequested = true;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// This is needed to wakeup the loop if we are called from inside of BeforeWait hook
|
|
});
|
|
}
|
|
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
class PlatformThreadingInterface : public ComSingleObject<IAvnPlatformThreadingInterface, &IID_IAvnPlatformThreadingInterface>
|
|
{
|
|
private:
|
|
ComPtr<IAvnPlatformThreadingInterfaceEvents> _events;
|
|
Signaler* _signaler;
|
|
ObserverHolder* _currentObserver;
|
|
public:
|
|
FORWARD_IUNKNOWN()
|
|
PlatformThreadingInterface()
|
|
{
|
|
_signaler = [Signaler new];
|
|
_currentObserver = _signaler->Observer;
|
|
};
|
|
|
|
~PlatformThreadingInterface()
|
|
{
|
|
[_signaler destroyObserver];
|
|
}
|
|
|
|
bool GetCurrentThreadIsLoopThread() override
|
|
{
|
|
return [NSThread isMainThread];
|
|
};
|
|
|
|
|
|
|
|
void SetEvents(IAvnPlatformThreadingInterfaceEvents *cb) override
|
|
{
|
|
_events = cb;
|
|
[_signaler setEvents:cb];
|
|
};
|
|
|
|
IAvnLoopCancellation *CreateLoopCancellation() override
|
|
{
|
|
return new LoopCancellation();
|
|
};
|
|
|
|
void RunLoop(IAvnLoopCancellation *cancel) override
|
|
{
|
|
START_COM_CALL;
|
|
auto can = dynamic_cast<LoopCancellation*>(cancel);
|
|
if(can->Cancelled)
|
|
return;
|
|
can->Running = true;
|
|
if(![NSApp isRunning])
|
|
{
|
|
can->IsApp = true;
|
|
[NSApp run];
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
@autoreleasepool {
|
|
auto previousObserver = _currentObserver;
|
|
if(_currentObserver->State->InsideCallback)
|
|
_currentObserver = [_signaler createObserver];
|
|
|
|
while(!can->Cancelled)
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
NSEvent* ev = [NSApp
|
|
nextEventMatchingMask:NSEventMaskAny
|
|
untilDate: [NSDate dateWithTimeIntervalSinceNow:1]
|
|
inMode:NSDefaultRunLoopMode
|
|
dequeue:true];
|
|
if(ev != NULL)
|
|
[NSApp sendEvent:ev];
|
|
}
|
|
}
|
|
_currentObserver = previousObserver;
|
|
}
|
|
}
|
|
};
|
|
|
|
void Signal() override
|
|
{
|
|
[_signaler signal];
|
|
};
|
|
|
|
void UpdateTimer(int ms) override
|
|
{
|
|
[_signaler updateTimer:ms];
|
|
};
|
|
|
|
void RequestBackgroundProcessing() override {
|
|
[_signaler requestBackgroundProcessing];
|
|
}
|
|
|
|
|
|
};
|
|
|
|
extern IAvnPlatformThreadingInterface* CreatePlatformThreading()
|
|
{
|
|
return new PlatformThreadingInterface();
|
|
}
|
|
|