This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
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.
575 lines
15 KiB
575 lines
15 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
|
|
#import "RCTSurface.h" |
|
#import "RCTSurfaceView+Internal.h" |
|
|
|
#import <mutex> |
|
#import <stdatomic.h> |
|
|
|
#import "RCTAssert.h" |
|
#import "RCTBridge+Private.h" |
|
#import "RCTBridge.h" |
|
#import "RCTShadowView+Layout.h" |
|
#import "RCTSurfaceDelegate.h" |
|
#import "RCTSurfaceRootShadowView.h" |
|
#import "RCTSurfaceRootShadowViewDelegate.h" |
|
#import "RCTSurfaceRootView.h" |
|
#import "RCTSurfaceView.h" |
|
#import "RCTTouchHandler.h" |
|
#import "RCTUIManager.h" |
|
#import "RCTUIManagerObserverCoordinator.h" |
|
#import "RCTUIManagerUtils.h" |
|
|
|
@interface RCTSurface () <RCTSurfaceRootShadowViewDelegate, RCTUIManagerObserver> |
|
@end |
|
|
|
@implementation RCTSurface { |
|
// Immutable |
|
RCTBridge *_bridge; |
|
NSString *_moduleName; |
|
NSNumber *_rootViewTag; |
|
|
|
// Protected by the `_mutex` |
|
std::mutex _mutex; |
|
RCTBridge *_batchedBridge; |
|
RCTSurfaceStage _stage; |
|
NSDictionary *_properties; |
|
CGSize _minimumSize; |
|
CGSize _maximumSize; |
|
CGSize _intrinsicSize; |
|
RCTUIManagerMountingBlock _mountingBlock; |
|
|
|
// The Main thread only |
|
RCTSurfaceView *_Nullable _view; |
|
RCTTouchHandler *_Nullable _touchHandler; |
|
|
|
// Semaphores |
|
dispatch_semaphore_t _rootShadowViewDidStartRenderingSemaphore; |
|
dispatch_semaphore_t _rootShadowViewDidStartLayingOutSemaphore; |
|
dispatch_semaphore_t _uiManagerDidPerformMountingSemaphore; |
|
|
|
// Atomics |
|
atomic_bool _waitingForMountingStageOnMainQueue; |
|
} |
|
|
|
|
|
- (instancetype)initWithBridge:(RCTBridge *)bridge |
|
moduleName:(NSString *)moduleName |
|
initialProperties:(NSDictionary *)initialProperties |
|
{ |
|
RCTAssert(bridge.valid, @"Valid bridge is required to instanciate `RCTSurface`."); |
|
|
|
if (self = [super init]) { |
|
_bridge = bridge; |
|
_batchedBridge = [_bridge batchedBridge] ?: _bridge; |
|
_moduleName = moduleName; |
|
_properties = [initialProperties copy]; |
|
_rootViewTag = RCTAllocateRootViewTag(); |
|
|
|
_rootShadowViewDidStartRenderingSemaphore = dispatch_semaphore_create(0); |
|
_rootShadowViewDidStartLayingOutSemaphore = dispatch_semaphore_create(0); |
|
_uiManagerDidPerformMountingSemaphore = dispatch_semaphore_create(0); |
|
|
|
_minimumSize = CGSizeZero; |
|
_maximumSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(handleBridgeWillLoadJavaScriptNotification:) |
|
name:RCTJavaScriptWillStartLoadingNotification |
|
object:_bridge]; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(handleBridgeDidLoadJavaScriptNotification:) |
|
name:RCTJavaScriptDidLoadNotification |
|
object:_bridge]; |
|
|
|
_stage = RCTSurfaceStageSurfaceDidInitialize; |
|
|
|
if (!bridge.loading) { |
|
_stage = _stage | RCTSurfaceStageBridgeDidLoad; |
|
} |
|
|
|
[_bridge.uiManager.observerCoordinator addObserver:self]; |
|
|
|
[self _registerRootView]; |
|
[self _run]; |
|
} |
|
|
|
return self; |
|
} |
|
|
|
- (void)dealloc |
|
{ |
|
[self _stop]; |
|
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
} |
|
|
|
#pragma mark - Immutable Properties (no need to enforce synchonization) |
|
|
|
- (RCTBridge *)bridge |
|
{ |
|
return _bridge; |
|
} |
|
|
|
- (NSString *)moduleName |
|
{ |
|
return _moduleName; |
|
} |
|
|
|
- (NSNumber *)rootViewTag |
|
{ |
|
return _rootViewTag; |
|
} |
|
|
|
#pragma mark - Convinience Internal Thread-Safe Properties |
|
|
|
- (RCTBridge *)_batchedBridge |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
return _batchedBridge; |
|
} |
|
|
|
- (RCTUIManager *)_uiManager |
|
{ |
|
return self._batchedBridge.uiManager; |
|
} |
|
|
|
#pragma mark - Main-Threaded Routines |
|
|
|
- (RCTSurfaceView *)view |
|
{ |
|
RCTAssertMainQueue(); |
|
|
|
if (!_view) { |
|
_view = [[RCTSurfaceView alloc] initWithSurface:self]; |
|
|
|
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:self.bridge]; |
|
[_touchHandler attachToView:_view]; |
|
|
|
[self _mountRootViewIfNeeded]; |
|
} |
|
|
|
return _view; |
|
} |
|
|
|
- (void)_mountRootViewIfNeeded |
|
{ |
|
RCTAssertMainQueue(); |
|
|
|
RCTSurfaceView *view = self->_view; |
|
if (!view) { |
|
return; |
|
} |
|
|
|
RCTSurfaceRootView *rootView = |
|
(RCTSurfaceRootView *)[self._uiManager viewForReactTag:self->_rootViewTag]; |
|
if (!rootView) { |
|
return; |
|
} |
|
|
|
RCTAssert([rootView isKindOfClass:[RCTSurfaceRootView class]], |
|
@"Received root view is not an instanse of `RCTSurfaceRootView`."); |
|
|
|
if (rootView.superview != view) { |
|
view.rootView = rootView; |
|
} |
|
} |
|
|
|
#pragma mark - Bridge Events |
|
|
|
- (void)handleBridgeWillLoadJavaScriptNotification:(NSNotification *)notification |
|
{ |
|
RCTAssertMainQueue(); |
|
|
|
// Reset states because the bridge is reloading. This is similar to initialization phase. |
|
_stage = RCTSurfaceStageSurfaceDidInitialize; |
|
_view = nil; |
|
_touchHandler = nil; |
|
[self _setStage:RCTSurfaceStageBridgeDidLoad]; |
|
} |
|
|
|
- (void)handleBridgeDidLoadJavaScriptNotification:(NSNotification *)notification |
|
{ |
|
RCTAssertMainQueue(); |
|
|
|
[self _setStage:RCTSurfaceStageModuleDidLoad]; |
|
|
|
RCTBridge *bridge = notification.userInfo[@"bridge"]; |
|
|
|
BOOL isRerunNeeded = NO; |
|
|
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
|
|
if (bridge != _batchedBridge) { |
|
_batchedBridge = bridge; |
|
isRerunNeeded = YES; |
|
} |
|
} |
|
|
|
if (isRerunNeeded) { |
|
[self _registerRootView]; |
|
[self _run]; |
|
} |
|
} |
|
|
|
#pragma mark - Stage management |
|
|
|
- (RCTSurfaceStage)stage |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
return _stage; |
|
} |
|
|
|
- (void)_setStage:(RCTSurfaceStage)stage |
|
{ |
|
RCTSurfaceStage updatedStage; |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
|
|
if (_stage & stage) { |
|
return; |
|
} |
|
|
|
updatedStage = (RCTSurfaceStage)(_stage | stage); |
|
_stage = updatedStage; |
|
} |
|
|
|
[self _propagateStageChange:updatedStage]; |
|
} |
|
|
|
- (void)_propagateStageChange:(RCTSurfaceStage)stage |
|
{ |
|
// Updating the `view` |
|
RCTExecuteOnMainQueue(^{ |
|
self->_view.stage = stage; |
|
}); |
|
|
|
// Notifying the `delegate` |
|
id<RCTSurfaceDelegate> delegate = self.delegate; |
|
if ([delegate respondsToSelector:@selector(surface:didChangeStage:)]) { |
|
[delegate surface:self didChangeStage:stage]; |
|
} |
|
} |
|
|
|
#pragma mark - Properties Management |
|
|
|
- (NSDictionary *)properties |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
return _properties; |
|
} |
|
|
|
- (void)setProperties:(NSDictionary *)properties |
|
{ |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
|
|
if ([properties isEqualToDictionary:_properties]) { |
|
return; |
|
} |
|
|
|
_properties = [properties copy]; |
|
} |
|
|
|
[self _run]; |
|
} |
|
|
|
#pragma mark - Running |
|
|
|
- (void)_run |
|
{ |
|
RCTBridge *batchedBridge; |
|
NSDictionary *properties; |
|
|
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
|
|
batchedBridge = _batchedBridge; |
|
properties = _properties; |
|
} |
|
|
|
if (!batchedBridge.valid) { |
|
return; |
|
} |
|
|
|
NSDictionary *applicationParameters = |
|
@{ |
|
@"rootTag": _rootViewTag, |
|
@"initialProps": properties, |
|
}; |
|
|
|
RCTLogInfo(@"Running surface %@ (%@)", _moduleName, applicationParameters); |
|
|
|
[self mountReactComponentWithBridge:batchedBridge moduleName:_moduleName params:applicationParameters]; |
|
|
|
[self _setStage:RCTSurfaceStageSurfaceDidRun]; |
|
} |
|
|
|
- (void)_stop |
|
{ |
|
[self unmountReactComponentWithBridge:self._batchedBridge rootViewTag:self->_rootViewTag]; |
|
} |
|
|
|
- (void)_registerRootView |
|
{ |
|
RCTBridge *batchedBridge; |
|
CGSize minimumSize; |
|
CGSize maximumSize; |
|
|
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
batchedBridge = _batchedBridge; |
|
minimumSize = _minimumSize; |
|
maximumSize = _maximumSize; |
|
} |
|
|
|
RCTUIManager *uiManager = batchedBridge.uiManager; |
|
|
|
// If we are on the main queue now, we have to proceed synchronously. |
|
// Otherwise, we cannot perform synchronous waiting for some stages later. |
|
(RCTIsMainQueue() ? RCTUnsafeExecuteOnUIManagerQueueSync : RCTExecuteOnUIManagerQueue)(^{ |
|
[uiManager registerRootViewTag:self->_rootViewTag]; |
|
|
|
RCTSurfaceRootShadowView *rootShadowView = |
|
(RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag]; |
|
RCTAssert([rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]], |
|
@"Received shadow view is not an instanse of `RCTSurfaceRootShadowView`."); |
|
|
|
[rootShadowView setMinimumSize:minimumSize |
|
maximumSize:maximumSize]; |
|
rootShadowView.delegate = self; |
|
}); |
|
} |
|
|
|
#pragma mark - Layout |
|
|
|
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize |
|
maximumSize:(CGSize)maximumSize |
|
{ |
|
RCTUIManager *uiManager = self._uiManager; |
|
__block CGSize fittingSize; |
|
|
|
RCTUnsafeExecuteOnUIManagerQueueSync(^{ |
|
RCTSurfaceRootShadowView *rootShadowView = |
|
(RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag]; |
|
|
|
RCTAssert([rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]], |
|
@"Received shadow view is not an instanse of `RCTSurfaceRootShadowView`."); |
|
|
|
fittingSize = [rootShadowView sizeThatFitsMinimumSize:minimumSize |
|
maximumSize:maximumSize]; |
|
}); |
|
|
|
return fittingSize; |
|
} |
|
|
|
#pragma mark - Size Constraints |
|
|
|
- (void)setSize:(CGSize)size |
|
{ |
|
[self setMinimumSize:size maximumSize:size]; |
|
} |
|
|
|
- (void)setMinimumSize:(CGSize)minimumSize |
|
maximumSize:(CGSize)maximumSize |
|
{ |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
if (CGSizeEqualToSize(minimumSize, _minimumSize) && |
|
CGSizeEqualToSize(maximumSize, _maximumSize)) { |
|
return; |
|
} |
|
|
|
_maximumSize = maximumSize; |
|
_minimumSize = minimumSize; |
|
} |
|
|
|
RCTUIManager *uiManager = self._uiManager; |
|
|
|
RCTUnsafeExecuteOnUIManagerQueueSync(^{ |
|
RCTSurfaceRootShadowView *rootShadowView = |
|
(RCTSurfaceRootShadowView *)[uiManager shadowViewForReactTag:self->_rootViewTag]; |
|
RCTAssert([rootShadowView isKindOfClass:[RCTSurfaceRootShadowView class]], |
|
@"Received shadow view is not an instanse of `RCTSurfaceRootShadowView`."); |
|
|
|
[rootShadowView setMinimumSize:minimumSize maximumSize:maximumSize]; |
|
[uiManager setNeedsLayout]; |
|
}); |
|
} |
|
|
|
- (CGSize)minimumSize |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
return _minimumSize; |
|
} |
|
|
|
- (CGSize)maximumSize |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
return _maximumSize; |
|
} |
|
|
|
#pragma mark - intrinsicSize |
|
|
|
- (void)setIntrinsicSize:(CGSize)intrinsicSize |
|
{ |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
if (CGSizeEqualToSize(intrinsicSize, _intrinsicSize)) { |
|
return; |
|
} |
|
|
|
_intrinsicSize = intrinsicSize; |
|
} |
|
|
|
// Notifying `delegate` |
|
id<RCTSurfaceDelegate> delegate = self.delegate; |
|
if ([delegate respondsToSelector:@selector(surface:didChangeIntrinsicSize:)]) { |
|
[delegate surface:self didChangeIntrinsicSize:intrinsicSize]; |
|
} |
|
} |
|
|
|
- (CGSize)intrinsicSize |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
return _intrinsicSize; |
|
} |
|
|
|
#pragma mark - Synchronous Waiting |
|
|
|
- (BOOL)synchronouslyWaitForStage:(RCTSurfaceStage)stage timeout:(NSTimeInterval)timeout |
|
{ |
|
if (RCTIsUIManagerQueue()) { |
|
RCTLogInfo(@"Synchronous waiting is not supported on UIManager queue."); |
|
return NO; |
|
} |
|
|
|
if (RCTIsMainQueue() && (stage == RCTSurfaceStageSurfaceDidInitialMounting)) { |
|
// All main-threaded execution (especially mounting process) has to be |
|
// intercepted, captured and performed synchnously at the end of this method |
|
// right after the semaphore signals. |
|
|
|
// Atomic variant of `_waitingForMountingStageOnMainQueue = YES;` |
|
atomic_fetch_or(&_waitingForMountingStageOnMainQueue, 1); |
|
} |
|
|
|
dispatch_semaphore_t semaphore; |
|
switch (stage) { |
|
case RCTSurfaceStageSurfaceDidInitialLayout: |
|
semaphore = _rootShadowViewDidStartLayingOutSemaphore; |
|
break; |
|
case RCTSurfaceStageSurfaceDidInitialRendering: |
|
semaphore = _rootShadowViewDidStartRenderingSemaphore; |
|
break; |
|
case RCTSurfaceStageSurfaceDidInitialMounting: |
|
semaphore = _uiManagerDidPerformMountingSemaphore; |
|
break; |
|
default: |
|
RCTAssert(NO, @"Only waiting for `RCTSurfaceStageSurfaceDidInitialRendering`, `RCTSurfaceStageSurfaceDidInitialLayout` and `RCTSurfaceStageSurfaceDidInitialMounting` stages are supported."); |
|
} |
|
|
|
BOOL timeoutOccurred = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC)); |
|
|
|
// Atomic equivalent of `_waitingForMountingStageOnMainQueue = NO;`. |
|
atomic_fetch_and(&_waitingForMountingStageOnMainQueue, 0); |
|
|
|
if (!timeoutOccurred) { |
|
// Balancing the semaphore. |
|
// Note: `dispatch_semaphore_wait` reverts the decrement in case when timeout occurred. |
|
dispatch_semaphore_signal(semaphore); |
|
} |
|
|
|
if (RCTIsMainQueue() && (stage == RCTSurfaceStageSurfaceDidInitialMounting)) { |
|
// Time to apply captured mounting block. |
|
RCTUIManagerMountingBlock mountingBlock; |
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
mountingBlock = _mountingBlock; |
|
_mountingBlock = nil; |
|
} |
|
|
|
if (mountingBlock) { |
|
mountingBlock(); |
|
[self _mountRootViewIfNeeded]; |
|
} |
|
} |
|
|
|
return !timeoutOccurred; |
|
} |
|
|
|
#pragma mark - RCTSurfaceRootShadowViewDelegate |
|
|
|
- (void)rootShadowView:(RCTRootShadowView *)rootShadowView didChangeIntrinsicSize:(CGSize)intrinsicSize |
|
{ |
|
self.intrinsicSize = intrinsicSize; |
|
} |
|
|
|
- (void)rootShadowViewDidStartRendering:(RCTSurfaceRootShadowView *)rootShadowView |
|
{ |
|
[self _setStage:RCTSurfaceStageSurfaceDidInitialRendering]; |
|
|
|
dispatch_semaphore_signal(_rootShadowViewDidStartRenderingSemaphore); |
|
} |
|
|
|
- (void)rootShadowViewDidStartLayingOut:(RCTSurfaceRootShadowView *)rootShadowView |
|
{ |
|
[self _setStage:RCTSurfaceStageSurfaceDidInitialLayout]; |
|
|
|
dispatch_semaphore_signal(_rootShadowViewDidStartLayingOutSemaphore); |
|
|
|
RCTExecuteOnMainQueue(^{ |
|
// Rendering is happening, let's mount `rootView` into `view` if we already didn't do this. |
|
[self _mountRootViewIfNeeded]; |
|
}); |
|
} |
|
|
|
#pragma mark - RCTUIManagerObserver |
|
|
|
- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block |
|
{ |
|
if (atomic_load(&_waitingForMountingStageOnMainQueue) && (self.stage & RCTSurfaceStageSurfaceDidInitialLayout)) { |
|
// Atomic equivalent of `_waitingForMountingStageOnMainQueue = NO;`. |
|
atomic_fetch_and(&_waitingForMountingStageOnMainQueue, 0); |
|
|
|
{ |
|
std::lock_guard<std::mutex> lock(_mutex); |
|
_mountingBlock = block; |
|
} |
|
return YES; |
|
} |
|
|
|
return NO; |
|
} |
|
|
|
- (void)uiManagerDidPerformMounting:(RCTUIManager *)manager |
|
{ |
|
if (self.stage & RCTSurfaceStageSurfaceDidInitialLayout) { |
|
[self _setStage:RCTSurfaceStageSurfaceDidInitialMounting]; |
|
dispatch_semaphore_signal(_uiManagerDidPerformMountingSemaphore); |
|
|
|
// No need to listen to UIManager anymore. |
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ |
|
[self->_bridge.uiManager.observerCoordinator removeObserver:self]; |
|
}); |
|
} |
|
} |
|
|
|
#pragma mark - Mounting/Unmounting of React components |
|
|
|
- (void)mountReactComponentWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName params:(NSDictionary *)params |
|
{ |
|
[bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[moduleName, params] completion:NULL]; |
|
} |
|
|
|
- (void)unmountReactComponentWithBridge:(RCTBridge *)bridge rootViewTag:(NSNumber *)rootViewTag |
|
{ |
|
[bridge enqueueJSCall:@"AppRegistry" method:@"unmountApplicationComponentAtRootTag" args:@[rootViewTag] completion:NULL]; |
|
} |
|
|
|
@end
|
|
|