Merge pull request #557 from cdavis5e/surface-loss

Handle surface loss.
This commit is contained in:
Bill Hollings 2019-04-01 21:55:27 -04:00 committed by GitHub
commit 7fd61a4610
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 198 additions and 5 deletions

View File

@ -9,6 +9,10 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
45003E73214AD4E500E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; }; 45003E73214AD4E500E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; };
45003E74214AD4E600E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; }; 45003E74214AD4E600E989CB /* MVKExtensions.def in Headers */ = {isa = PBXBuildFile; fileRef = 45003E6F214AD4C900E989CB /* MVKExtensions.def */; };
4553AEFB2251617100E8EBCD /* MVKBlockObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 4553AEF62251617100E8EBCD /* MVKBlockObserver.m */; };
4553AEFC2251617100E8EBCD /* MVKBlockObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 4553AEF62251617100E8EBCD /* MVKBlockObserver.m */; };
4553AEFD2251617100E8EBCD /* MVKBlockObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 4553AEFA2251617100E8EBCD /* MVKBlockObserver.h */; };
4553AEFE2251617100E8EBCD /* MVKBlockObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 4553AEFA2251617100E8EBCD /* MVKBlockObserver.h */; };
45557A5221C9EFF3008868BD /* MVKCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45557A4D21C9EFF3008868BD /* MVKCodec.cpp */; }; 45557A5221C9EFF3008868BD /* MVKCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45557A4D21C9EFF3008868BD /* MVKCodec.cpp */; };
45557A5321C9EFF3008868BD /* MVKCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45557A4D21C9EFF3008868BD /* MVKCodec.cpp */; }; 45557A5321C9EFF3008868BD /* MVKCodec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45557A4D21C9EFF3008868BD /* MVKCodec.cpp */; };
45557A5421C9EFF3008868BD /* MVKCodec.h in Headers */ = {isa = PBXBuildFile; fileRef = 45557A5121C9EFF3008868BD /* MVKCodec.h */; }; 45557A5421C9EFF3008868BD /* MVKCodec.h in Headers */ = {isa = PBXBuildFile; fileRef = 45557A5121C9EFF3008868BD /* MVKCodec.h */; };
@ -271,6 +275,8 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
45003E6F214AD4C900E989CB /* MVKExtensions.def */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = MVKExtensions.def; sourceTree = "<group>"; }; 45003E6F214AD4C900E989CB /* MVKExtensions.def */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = MVKExtensions.def; sourceTree = "<group>"; };
4553AEF62251617100E8EBCD /* MVKBlockObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVKBlockObserver.m; sourceTree = "<group>"; };
4553AEFA2251617100E8EBCD /* MVKBlockObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKBlockObserver.h; sourceTree = "<group>"; };
45557A4D21C9EFF3008868BD /* MVKCodec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKCodec.cpp; sourceTree = "<group>"; }; 45557A4D21C9EFF3008868BD /* MVKCodec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKCodec.cpp; sourceTree = "<group>"; };
45557A5121C9EFF3008868BD /* MVKCodec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKCodec.h; sourceTree = "<group>"; }; 45557A5121C9EFF3008868BD /* MVKCodec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKCodec.h; sourceTree = "<group>"; };
45557A5721CD83C3008868BD /* MVKDXTnCodec.def */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = MVKDXTnCodec.def; sourceTree = "<group>"; }; 45557A5721CD83C3008868BD /* MVKDXTnCodec.def */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = MVKDXTnCodec.def; sourceTree = "<group>"; };
@ -508,6 +514,8 @@
A98149401FB6A3F7005F00B4 /* Utility */ = { A98149401FB6A3F7005F00B4 /* Utility */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4553AEFA2251617100E8EBCD /* MVKBlockObserver.h */,
4553AEF62251617100E8EBCD /* MVKBlockObserver.m */,
45557A5721CD83C3008868BD /* MVKDXTnCodec.def */, 45557A5721CD83C3008868BD /* MVKDXTnCodec.def */,
45557A4D21C9EFF3008868BD /* MVKCodec.cpp */, 45557A4D21C9EFF3008868BD /* MVKCodec.cpp */,
45557A5121C9EFF3008868BD /* MVKCodec.h */, 45557A5121C9EFF3008868BD /* MVKCodec.h */,
@ -654,6 +662,7 @@
A94FB7BC1C7DFB4800632CA3 /* MVKCmdPipeline.h in Headers */, A94FB7BC1C7DFB4800632CA3 /* MVKCmdPipeline.h in Headers */,
A94FB7F81C7DFB4800632CA3 /* MVKPipeline.h in Headers */, A94FB7F81C7DFB4800632CA3 /* MVKPipeline.h in Headers */,
A94FB7F01C7DFB4800632CA3 /* MVKImage.h in Headers */, A94FB7F01C7DFB4800632CA3 /* MVKImage.h in Headers */,
4553AEFD2251617100E8EBCD /* MVKBlockObserver.h in Headers */,
A94FB7B81C7DFB4800632CA3 /* MVKCmdTransfer.h in Headers */, A94FB7B81C7DFB4800632CA3 /* MVKCmdTransfer.h in Headers */,
A94FB7C81C7DFB4800632CA3 /* MVKCmdDraw.h in Headers */, A94FB7C81C7DFB4800632CA3 /* MVKCmdDraw.h in Headers */,
A94FB7D01C7DFB4800632CA3 /* MVKCommandBuffer.h in Headers */, A94FB7D01C7DFB4800632CA3 /* MVKCommandBuffer.h in Headers */,
@ -718,6 +727,7 @@
A94FB7BD1C7DFB4800632CA3 /* MVKCmdPipeline.h in Headers */, A94FB7BD1C7DFB4800632CA3 /* MVKCmdPipeline.h in Headers */,
A94FB7F91C7DFB4800632CA3 /* MVKPipeline.h in Headers */, A94FB7F91C7DFB4800632CA3 /* MVKPipeline.h in Headers */,
A94FB7F11C7DFB4800632CA3 /* MVKImage.h in Headers */, A94FB7F11C7DFB4800632CA3 /* MVKImage.h in Headers */,
4553AEFE2251617100E8EBCD /* MVKBlockObserver.h in Headers */,
A94FB7B91C7DFB4800632CA3 /* MVKCmdTransfer.h in Headers */, A94FB7B91C7DFB4800632CA3 /* MVKCmdTransfer.h in Headers */,
A94FB7C91C7DFB4800632CA3 /* MVKCmdDraw.h in Headers */, A94FB7C91C7DFB4800632CA3 /* MVKCmdDraw.h in Headers */,
A94FB7D11C7DFB4800632CA3 /* MVKCommandBuffer.h in Headers */, A94FB7D11C7DFB4800632CA3 /* MVKCommandBuffer.h in Headers */,
@ -929,6 +939,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4553AEFB2251617100E8EBCD /* MVKBlockObserver.m in Sources */,
A9E53DFF21064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.m in Sources */, A9E53DFF21064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.m in Sources */,
A94FB80A1C7DFB4800632CA3 /* MVKResource.mm in Sources */, A94FB80A1C7DFB4800632CA3 /* MVKResource.mm in Sources */,
A94FB7E21C7DFB4800632CA3 /* MVKDescriptorSet.mm in Sources */, A94FB7E21C7DFB4800632CA3 /* MVKDescriptorSet.mm in Sources */,
@ -983,6 +994,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4553AEFC2251617100E8EBCD /* MVKBlockObserver.m in Sources */,
A9E53E0021064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.m in Sources */, A9E53E0021064F84002781DD /* MTLRenderPipelineDescriptor+MoltenVK.m in Sources */,
A94FB80B1C7DFB4800632CA3 /* MVKResource.mm in Sources */, A94FB80B1C7DFB4800632CA3 /* MVKResource.mm in Sources */,
A94FB7E31C7DFB4800632CA3 /* MVKDescriptorSet.mm in Sources */, A94FB7E31C7DFB4800632CA3 /* MVKDescriptorSet.mm in Sources */,

View File

@ -390,7 +390,10 @@ MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKDevice* de
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) { for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
MVKSwapchain* mvkSC = (MVKSwapchain*)pPresentInfo->pSwapchains[i]; MVKSwapchain* mvkSC = (MVKSwapchain*)pPresentInfo->pSwapchains[i];
_surfaceImages.push_back(mvkSC->getImage(pPresentInfo->pImageIndices[i])); _surfaceImages.push_back(mvkSC->getImage(pPresentInfo->pImageIndices[i]));
if (mvkSC->getHasSurfaceSizeChanged()) { // Surface loss takes precedence over out-of-date errors.
if (mvkSC->getIsSurfaceLost()) {
_submissionResult = VK_ERROR_SURFACE_LOST_KHR;
} else if (mvkSC->getHasSurfaceSizeChanged() && _submissionResult != VK_ERROR_SURFACE_LOST_KHR) {
_submissionResult = VK_ERROR_OUT_OF_DATE_KHR; _submissionResult = VK_ERROR_OUT_OF_DATE_KHR;
} }
} }

View File

@ -20,6 +20,7 @@
#include "mvk_vulkan.h" #include "mvk_vulkan.h"
#include "MVKBaseObject.h" #include "MVKBaseObject.h"
#include <mutex>
// Expose MoltenVK Apple surface extension functionality // Expose MoltenVK Apple surface extension functionality
@ -27,14 +28,14 @@
# define vkCreate_PLATFORM_SurfaceMVK vkCreateIOSSurfaceMVK # define vkCreate_PLATFORM_SurfaceMVK vkCreateIOSSurfaceMVK
# define Vk_PLATFORM_SurfaceCreateInfoMVK VkIOSSurfaceCreateInfoMVK # define Vk_PLATFORM_SurfaceCreateInfoMVK VkIOSSurfaceCreateInfoMVK
# define PLATFORM_VIEW_CLASS UIView # define PLATFORM_VIEW_CLASS UIView
# include <UIKit/UIView.h> # import <UIKit/UIView.h>
#endif #endif
#ifdef VK_USE_PLATFORM_MACOS_MVK #ifdef VK_USE_PLATFORM_MACOS_MVK
# define vkCreate_PLATFORM_SurfaceMVK vkCreateMacOSSurfaceMVK # define vkCreate_PLATFORM_SurfaceMVK vkCreateMacOSSurfaceMVK
# define Vk_PLATFORM_SurfaceCreateInfoMVK VkMacOSSurfaceCreateInfoMVK # define Vk_PLATFORM_SurfaceCreateInfoMVK VkMacOSSurfaceCreateInfoMVK
# define PLATFORM_VIEW_CLASS NSView # define PLATFORM_VIEW_CLASS NSView
# include <AppKit/NSView.h> # import <AppKit/NSView.h>
#endif #endif
#import <Metal/Metal.h> #import <Metal/Metal.h>
@ -42,6 +43,8 @@
class MVKInstance; class MVKInstance;
@class MVKBlockObserver;
#pragma mark MVKSurface #pragma mark MVKSurface
@ -51,7 +54,10 @@ class MVKSurface : public MVKConfigurableObject {
public: public:
/** Returns the CAMetalLayer underlying this surface. */ /** Returns the CAMetalLayer underlying this surface. */
inline CAMetalLayer* getCAMetalLayer() { return _mtlCAMetalLayer; } inline CAMetalLayer* getCAMetalLayer() {
std::lock_guard<std::mutex> lock(_lock);
return _mtlCAMetalLayer;
}
#pragma mark Construction #pragma mark Construction
@ -64,5 +70,7 @@ public:
protected: protected:
CAMetalLayer* _mtlCAMetalLayer; CAMetalLayer* _mtlCAMetalLayer;
std::mutex _lock;
MVKBlockObserver* _layerObserver;
}; };

View File

@ -20,6 +20,7 @@
#include "MVKInstance.h" #include "MVKInstance.h"
#include "MVKFoundation.h" #include "MVKFoundation.h"
#include "MVKOSExtensions.h" #include "MVKOSExtensions.h"
#import "MVKBlockObserver.h"
#pragma mark MVKSurface #pragma mark MVKSurface
@ -57,6 +58,19 @@ MVKSurface::MVKSurface(MVKInstance* mvkInstance,
// Confirm that we were provided with a CAMetalLayer // Confirm that we were provided with a CAMetalLayer
if ([obj isKindOfClass: [CAMetalLayer class]]) { if ([obj isKindOfClass: [CAMetalLayer class]]) {
_mtlCAMetalLayer = (CAMetalLayer*)[obj retain]; // retained _mtlCAMetalLayer = (CAMetalLayer*)[obj retain]; // retained
if ([_mtlCAMetalLayer.delegate isKindOfClass: [PLATFORM_VIEW_CLASS class]]) {
// Sometimes, the owning view can replace its CAMetalLayer. In that case, the client
// needs to recreate the surface.
_layerObserver = [MVKBlockObserver observerWithBlock: ^(NSString* path, id, NSDictionary*, void*) {
if ( ![path isEqualTo: @"layer"] ) { return; }
std::lock_guard<std::mutex> lock(this->_lock);
[this->_mtlCAMetalLayer release];
this->_mtlCAMetalLayer = nil;
this->setConfigurationResult(VK_ERROR_SURFACE_LOST_KHR);
[this->_layerObserver release];
this->_layerObserver = nil;
} forObject: _mtlCAMetalLayer.delegate atKeyPath: @"layer"];
}
} else { } else {
setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "%s(): On-screen rendering requires a layer of type CAMetalLayer.", mvkSurfaceCreateFuncName)); setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "%s(): On-screen rendering requires a layer of type CAMetalLayer.", mvkSurfaceCreateFuncName));
_mtlCAMetalLayer = nil; _mtlCAMetalLayer = nil;
@ -64,6 +78,8 @@ MVKSurface::MVKSurface(MVKInstance* mvkInstance,
} }
MVKSurface::~MVKSurface() { MVKSurface::~MVKSurface() {
std::lock_guard<std::mutex> lock(_lock);
[_mtlCAMetalLayer release]; [_mtlCAMetalLayer release];
[_layerObserver release];
} }

View File

@ -24,6 +24,8 @@
class MVKSwapchainImage; class MVKSwapchainImage;
class MVKWatermark; class MVKWatermark;
@class MVKBlockObserver;
#pragma mark MVKSwapchain #pragma mark MVKSwapchain
@ -63,6 +65,9 @@ public:
/** Returns whether the surface size has changed since the last time this function was called. */ /** Returns whether the surface size has changed since the last time this function was called. */
bool getHasSurfaceSizeChanged(); bool getHasSurfaceSizeChanged();
/** Returns whether the parent surface is now lost and this swapchain must be recreated. */
bool getIsSurfaceLost() { return _surfaceLost; }
/** Returns the specified performance stats structure. */ /** Returns the specified performance stats structure. */
const MVKSwapchainPerformance* getPerformanceStatistics() { return &_performanceStatistics; } const MVKSwapchainPerformance* getPerformanceStatistics() { return &_performanceStatistics; }
@ -106,5 +111,7 @@ protected:
uint64_t _lastFrameTime; uint64_t _lastFrameTime;
double _averageFrameIntervalFilterAlpha; double _averageFrameIntervalFilterAlpha;
uint32_t _currentPerfLogFrameCount; uint32_t _currentPerfLogFrameCount;
std::atomic<bool> _surfaceLost;
MVKBlockObserver* _layerObserver;
}; };

View File

@ -27,6 +27,7 @@
#include "mvk_datatypes.h" #include "mvk_datatypes.h"
#include "MVKLogging.h" #include "MVKLogging.h"
#import "CAMetalLayer+MoltenVK.h" #import "CAMetalLayer+MoltenVK.h"
#import "MVKBlockObserver.h"
using namespace std; using namespace std;
@ -65,6 +66,9 @@ VkResult MVKSwapchain::acquireNextImageKHR(uint64_t timeout,
VkFence fence, VkFence fence,
uint32_t deviceMask, uint32_t deviceMask,
uint32_t* pImageIndex) { uint32_t* pImageIndex) {
if ( getIsSurfaceLost() ) { return VK_ERROR_SURFACE_LOST_KHR; }
// Find the image that has the smallest availability measure // Find the image that has the smallest availability measure
uint32_t minWaitIndex = 0; uint32_t minWaitIndex = 0;
MVKSwapchainImageAvailability minAvailability = { .acquisitionID = kMVKUndefinedLargeUInt64, MVKSwapchainImageAvailability minAvailability = { .acquisitionID = kMVKUndefinedLargeUInt64,
@ -173,7 +177,7 @@ id<CAMetalDrawable> MVKSwapchain::getNextCAMetalDrawable() {
#pragma mark Construction #pragma mark Construction
MVKSwapchain::MVKSwapchain(MVKDevice* device, MVKSwapchain::MVKSwapchain(MVKDevice* device,
const VkSwapchainCreateInfoKHR* pCreateInfo) : MVKBaseDeviceObject(device) { const VkSwapchainCreateInfoKHR* pCreateInfo) : MVKBaseDeviceObject(device), _surfaceLost(false) {
_currentAcquisitionID = 0; _currentAcquisitionID = 0;
// If applicable, release any surfaces (not currently being displayed) from the old swapchain. // If applicable, release any surfaces (not currently being displayed) from the old swapchain.
@ -194,6 +198,12 @@ MVKSwapchain::MVKSwapchain(MVKDevice* device,
void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) { void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) {
MVKSurface* mvkSrfc = (MVKSurface*)pCreateInfo->surface; MVKSurface* mvkSrfc = (MVKSurface*)pCreateInfo->surface;
if ( !mvkSrfc->getCAMetalLayer() ) {
setConfigurationResult(mvkSrfc->getConfigurationResult());
_surfaceLost = true;
return;
}
_mtlLayer = mvkSrfc->getCAMetalLayer(); _mtlLayer = mvkSrfc->getCAMetalLayer();
_mtlLayer.device = getMTLDevice(); _mtlLayer.device = getMTLDevice();
_mtlLayer.pixelFormat = getMTLPixelFormatFromVkFormat(pCreateInfo->imageFormat); _mtlLayer.pixelFormat = getMTLPixelFormatFromVkFormat(pCreateInfo->imageFormat);
@ -211,12 +221,27 @@ void MVKSwapchain::initCAMetalLayer(const VkSwapchainCreateInfoKHR* pCreateInfo,
// - drawsAsynchronously // - drawsAsynchronously
// - colorspace (macOS only) Vulkan only supports sRGB colorspace for now. // - colorspace (macOS only) Vulkan only supports sRGB colorspace for now.
// - wantsExtendedDynamicRangeContent (macOS only) // - wantsExtendedDynamicRangeContent (macOS only)
if ( [_mtlLayer.delegate isKindOfClass: [PLATFORM_VIEW_CLASS class]] ) {
// Sometimes, the owning view can replace its CAMetalLayer. In that case, the client
// needs to recreate the swapchain, or no content will be displayed.
_layerObserver = [MVKBlockObserver observerWithBlock: ^(NSString* path, id, NSDictionary*, void*) {
if ( ![path isEqualTo: @"layer"] ) { return; }
this->_surfaceLost = true;
[this->_layerObserver release];
this->_layerObserver = nil;
} forObject: _mtlLayer.delegate atKeyPath: @"layer"];
}
} }
// Initializes the array of images used for the surface of this swapchain. // Initializes the array of images used for the surface of this swapchain.
// The CAMetalLayer should already be initialized when this is called. // The CAMetalLayer should already be initialized when this is called.
void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) { void MVKSwapchain::initSurfaceImages(const VkSwapchainCreateInfoKHR* pCreateInfo, uint32_t imgCnt) {
if ( getIsSurfaceLost() ) {
return;
}
VkExtent2D imgExtent = mvkVkExtent2DFromCGSize(_mtlLayerOrigDrawSize); VkExtent2D imgExtent = mvkVkExtent2DFromCGSize(_mtlLayerOrigDrawSize);
VkImageCreateInfo imgInfo = { VkImageCreateInfo imgInfo = {
@ -263,5 +288,6 @@ MVKSwapchain::~MVKSwapchain() {
for (auto& img : _surfaceImages) { _device->destroySwapchainImage(img, NULL); } for (auto& img : _surfaceImages) { _device->destroySwapchainImage(img, NULL); }
if (_licenseWatermark) { _licenseWatermark->destroy(); } if (_licenseWatermark) { _licenseWatermark->destroy(); }
[this->_layerObserver release];
} }

View File

@ -0,0 +1,47 @@
/*
* MVKBlockObserver.h
*
* Copyright (c) 2019 Chip Davis for CodeWeavers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/NSObject.h>
#import <Foundation/NSKeyValueObserving.h>
#pragma mark MVKBlockObserver
typedef void (^MVKKeyValueObserverBlock)(NSString* keyPath, id object, NSDictionary* changeDict, void* context);
/** Class that calls a block on any key-value observation callback. */
@interface MVKBlockObserver : NSObject {
MVKKeyValueObserverBlock _block;
id _target;
NSString* _keyPath;
}
- (instancetype)initWithBlock:(MVKKeyValueObserverBlock) block;
- (instancetype)initWithBlock:(MVKKeyValueObserverBlock) block forObject: object atKeyPath:(NSString*) keyPath;
+ (instancetype)observerWithBlock:(MVKKeyValueObserverBlock) block;
+ (instancetype)observerWithBlock:(MVKKeyValueObserverBlock) block forObject: object atKeyPath:(NSString*) keyPath;
- (void)observeValueForKeyPath:(NSString*) path ofObject: object change:(NSDictionary*) changeDict context: (void*)context;
- (void)startObservingObject: object atKeyPath:(NSString*) keyPath;
- (void)stopObserving;
@end

View File

@ -0,0 +1,74 @@
/*
* MVKBlockObserver.m
*
* Copyright (c) 2019 Chip Davis for CodeWeavers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MVKBlockObserver.h"
@implementation MVKBlockObserver
- (instancetype)initWithBlock:(MVKKeyValueObserverBlock) block {
if ((self = [super init])) {
_block = [block copy];
}
return self;
}
- (instancetype)initWithBlock:(MVKKeyValueObserverBlock) block forObject: object atKeyPath:(NSString*) keyPath {
if ((self = [super init])) {
_block = [block copy];
[self startObservingObject: object atKeyPath: keyPath];
}
return self;
}
+ (instancetype)observerWithBlock:(MVKKeyValueObserverBlock) block {
return [[self alloc] initWithBlock: block];
}
+ (instancetype)observerWithBlock:(MVKKeyValueObserverBlock) block forObject: object atKeyPath:(NSString*) keyPath {
return [[self alloc] initWithBlock: block forObject: object atKeyPath: keyPath];
}
- (void)dealloc {
[self stopObserving];
[super dealloc];
}
- (void)observeValueForKeyPath:(NSString*) path ofObject: object change:(NSDictionary*) changeDict context:(void*) context {
_block(path, object, changeDict, context);
}
- (void)startObservingObject: object atKeyPath:(NSString*) path {
if ( !_target ) {
_target = [object retain];
_keyPath = [path copy];
[_target addObserver: self forKeyPath: _keyPath options: 0 context: NULL];
}
}
- (void)stopObserving {
[_target removeObserver: self forKeyPath: _keyPath context: NULL];
[_target release];
[_keyPath release];
_target = nil;
_keyPath = nil;
}
@end