Files
MapLibreParticleLab/platform/ios/src/MLNParticleFieldRuntime.mm
tei 70f112ea27
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
fix: close STEP-085 V4 renderer efficiency
2026-09-19 22:16:36 +09:00

417 lines
34 KiB
Plaintext

#import "MLNParticleFieldRuntime.h"
#import "MLNMapView_Private.h"
#import <objc/runtime.h>
#include <mbgl/particle/field/field_profile_contract_json.hpp>
#include <mbgl/particle/field/field_data_adapter.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/layers/raster_particle_layer.hpp>
#include <mbgl/style/layers/raster_particle_layer_impl.hpp>
#include <mbgl/style/layers/weather_color_layer.hpp>
#include <mbgl/style/layers/weather_color_layer_impl.hpp>
#include <mbgl/style/sources/raster_array_source.hpp>
#include <cmath>
#include <array>
#include <vector>
NSErrorDomain const MLNParticleFieldRuntimeErrorDomain = @"org.maplibre.MLNParticleFieldRuntime";
namespace {
constexpr NSUInteger MaxEncodedIdentifierUTF8Bytes = 64;
constexpr NSUInteger MaxContractUTF8Bytes = 256 * 1024;
constexpr NSUInteger MaxQbinBytes = 64 * 1024 * 1024;
constexpr NSUInteger MaxMetadataUTF8Bytes = 1024 * 1024;
void setError(NSError **error, MLNParticleFieldRuntimeErrorCode code, NSString *description) {
if (error) *error = [NSError errorWithDomain:MLNParticleFieldRuntimeErrorDomain
code:code
userInfo:@{NSLocalizedDescriptionKey: description}];
}
bool hasValidIdentifier(NSString *value) {
return value && value.length > 0 && [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= MaxEncodedIdentifierUTF8Bytes;
}
bool hasValidContract(NSString *value) {
return value && value.length > 0 && [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= MaxContractUTF8Bytes;
}
std::string stringFromNSString(NSString *value) {
const char *utf8 = value.UTF8String;
return utf8 ? std::string(utf8) : std::string();
}
MLNParticleFieldRuntimeCommandResult commandResult(bool accepted) {
return accepted ? MLNParticleFieldRuntimeCommandResultAccepted : MLNParticleFieldRuntimeCommandResultRejected;
}
bool validFloat(float value) { return std::isfinite(value); }
std::vector<char> charsFromData(NSData *data) {
std::vector<char> value(data.length);
[data getBytes:value.data() length:value.size()];
return value;
}
} // namespace
@implementation MLNParticleWeatherColorStop
- (instancetype)initWithValue:(float)value red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha {
if ((self = [super init])) { _value = value; _red = red; _green = green; _blue = blue; _alpha = alpha; }
return self;
}
@end
@implementation MLNParticleFieldRuntimeConfiguration
- (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier
weatherColorLayerIdentifier:(NSString *)weatherColorLayerIdentifier
particleLayerIdentifier:(NSString *)particleLayerIdentifier {
NSParameterAssert(sourceIdentifier.length > 0);
NSParameterAssert(weatherColorLayerIdentifier.length > 0);
NSParameterAssert(particleLayerIdentifier.length > 0);
if (sourceIdentifier.length == 0 || weatherColorLayerIdentifier.length == 0 || particleLayerIdentifier.length == 0) {
[NSException raise:NSInvalidArgumentException format:@"Particle field runtime identifiers must be non-empty."];
}
if ((self = [super init])) {
_sourceIdentifier = [sourceIdentifier copy];
_weatherColorLayerIdentifier = [weatherColorLayerIdentifier copy];
_particleLayerIdentifier = [particleLayerIdentifier copy];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
return [[[self class] allocWithZone:zone] initWithSourceIdentifier:_sourceIdentifier
weatherColorLayerIdentifier:_weatherColorLayerIdentifier
particleLayerIdentifier:_particleLayerIdentifier];
}
@end
@interface MLNParticleFieldRuntime () {
__weak MLNMapView *_mapView;
BOOL _disposed;
MLNParticleFieldRuntimeState _state;
MLNParticleFieldRuntimePlaybackState _playbackState;
MLNParticleFieldRuntimeTargetFps _targetFps;
BOOL _weatherColorVisible;
BOOL _particleVisible;
NSString *_v4ParticleBindingFieldKey;
NSMutableSet<NSString *> *_submittedEncodedFieldKeys;
NSTimer *_v4CadenceTimer;
}
- (BOOL)isV4ParticleCadenceReady;
- (void)refreshV4CadenceWake;
- (void)runV4CadenceWake:(NSTimer *)timer;
- (instancetype)initWithMapView:(MLNMapView *)mapView
configuration:(MLNParticleFieldRuntimeConfiguration *)configuration
error:(NSError **)error;
@end
@implementation MLNParticleFieldRuntime
- (instancetype)initWithMapView:(MLNMapView *)mapView
configuration:(MLNParticleFieldRuntimeConfiguration *)configuration
error:(NSError **)error {
if ((self = [super init])) {
_mapView = mapView;
_configuration = [configuration copy];
_state = MLNParticleFieldRuntimeStateWaitingForFields;
_playbackState = MLNParticleFieldRuntimePlaybackStatePause;
_targetFps = MLNParticleFieldRuntimeTargetFpsAuto;
_weatherColorVisible = YES;
_particleVisible = YES;
_submittedEncodedFieldKeys = [NSMutableSet set];
if (![self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { return true; }]) return nil;
}
return self;
}
- (BOOL)isDisposed { return _disposed; }
- (MLNParticleFieldRuntimeState)state { return _state; }
- (MLNParticleFieldRuntimePlaybackState)playbackState { return _playbackState; }
- (MLNParticleFieldRuntimeTargetFps)targetFps { return _targetFps; }
- (void)dispose {
[_v4CadenceTimer invalidate];
_v4CadenceTimer = nil;
_disposed = YES;
_mapView = nil;
}
- (void)refreshV4CadenceWake {
[_v4CadenceTimer invalidate];
_v4CadenceTimer = nil;
if (_disposed || ![self isV4ParticleCadenceReady] || !_particleVisible ||
_targetFps == MLNParticleFieldRuntimeTargetFpsAuto) return;
const NSTimeInterval interval = _targetFps == MLNParticleFieldRuntimeTargetFps30 ? 0.034 : 0.017;
_v4CadenceTimer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(runV4CadenceWake:)
userInfo:nil
repeats:NO];
}
- (void)runV4CadenceWake:(NSTimer *)timer {
if (timer != _v4CadenceTimer) return;
_v4CadenceTimer = nil;
if (_disposed || ![self isV4ParticleCadenceReady] || !_particleVisible) return;
[_mapView triggerRepaint];
[self refreshV4CadenceWake];
}
- (BOOL)isV4ParticleCadenceReady {
return _state != MLNParticleFieldRuntimeStateDisabled && _state != MLNParticleFieldRuntimeStateError && _v4ParticleBindingFieldKey &&
[_submittedEncodedFieldKeys containsObject:_v4ParticleBindingFieldKey];
}
- (BOOL)withCurrentSource:(NSError **)error action:(bool (^)(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &))action {
if (_disposed) {
setError(error, MLNParticleFieldRuntimeErrorDisposed, @"Particle field runtime has been disposed.");
return NO;
}
MLNMapView *mapView = _mapView;
if (!mapView) {
setError(error, MLNParticleFieldRuntimeErrorMapUnavailable, @"The owning map view is no longer available.");
return NO;
}
@try {
auto& style = [mapView mbglMap].getStyle();
const auto sourceID = stringFromNSString(_configuration.sourceIdentifier);
auto *untypedSource = style.getSource(sourceID);
auto *source = untypedSource ? untypedSource->as<mbgl::style::RasterArraySource>() : nullptr;
if (!source) {
setError(error, untypedSource ? MLNParticleFieldRuntimeErrorSourceTypeMismatch : MLNParticleFieldRuntimeErrorSourceMissing,
untypedSource ? @"The configured source is not a raster-array source." : @"The configured raster-array source is missing from the current style.");
return NO;
}
const auto weatherID = stringFromNSString(_configuration.weatherColorLayerIdentifier);
auto *untypedWeather = style.getLayer(weatherID);
auto *weather = untypedWeather && mbgl::style::layerTypeInfoEquals(untypedWeather->getTypeInfo(), mbgl::style::WeatherColorLayer::Impl::staticTypeInfo())
? static_cast<mbgl::style::WeatherColorLayer *>(untypedWeather) : nullptr;
if (!weather) {
setError(error, untypedWeather ? MLNParticleFieldRuntimeErrorWeatherColorLayerTypeMismatch : MLNParticleFieldRuntimeErrorWeatherColorLayerMissing,
untypedWeather ? @"The configured weather-color layer has the wrong Core type." : @"The configured weather-color layer is missing from the current style.");
return NO;
}
const auto particleID = stringFromNSString(_configuration.particleLayerIdentifier);
auto *untypedParticle = style.getLayer(particleID);
auto *particle = untypedParticle && mbgl::style::layerTypeInfoEquals(untypedParticle->getTypeInfo(), mbgl::style::RasterParticleLayer::Impl::staticTypeInfo())
? static_cast<mbgl::style::RasterParticleLayer *>(untypedParticle) : nullptr;
if (!particle) {
setError(error, untypedParticle ? MLNParticleFieldRuntimeErrorParticleLayerTypeMismatch : MLNParticleFieldRuntimeErrorParticleLayerMissing,
untypedParticle ? @"The configured raster-particle layer has the wrong Core type." : @"The configured raster-particle layer is missing from the current style.");
return NO;
}
if (weather->getSourceID() != sourceID || particle->getSourceID() != sourceID) {
setError(error, MLNParticleFieldRuntimeErrorLayerSourceMismatch, @"Configured particle layers do not reference the configured raster-array source.");
return NO;
}
return action(*source, *weather, *particle);
} @catch (NSException *exception) {
setError(error, MLNParticleFieldRuntimeErrorMapUnavailable, exception.reason ?: @"The underlying map is unavailable.");
return NO;
}
}
- (MLNParticleEncodedFieldSubmitResult)submitEncodedFieldWithFieldKey:(NSString *)fieldKey
fieldID:(NSString *)fieldID
generation:(uint64_t)generation
dataVersion:(uint64_t)dataVersion
contractJSON:(NSString *)contractJSON
qbin:(NSData *)qbin
error:(NSError **)error {
if (_state == MLNParticleFieldRuntimeStateDisabled) {
setError(error, MLNParticleFieldRuntimeErrorDisposed, @"Particle field runtime is disabled; enable it before submitting fields.");
return MLNParticleEncodedFieldSubmitResultNone;
}
if (!hasValidIdentifier(fieldKey) || !hasValidIdentifier(fieldID) || !hasValidContract(contractJSON) || generation == 0 || qbin.length == 0 || qbin.length > MaxQbinBytes) {
setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Encoded field arguments violate the V4 transport contract.");
return MLNParticleEncodedFieldSubmitResultNone;
}
const auto contract = mbgl::particle::parseResolvedFieldProfileContractJson(stringFromNSString(contractJSON));
if (!contract || !hasValidIdentifier([NSString stringWithUTF8String:contract->profileID.c_str()])) {
setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"contractJSON is not a valid V4 field profile contract.");
return MLNParticleEncodedFieldSubmitResultNone;
}
std::vector<uint8_t> bytes(qbin.length);
[qbin getBytes:bytes.data() length:bytes.size()];
__block MLNParticleEncodedFieldSubmitResult result = MLNParticleEncodedFieldSubmitResultNone;
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) {
const auto coreResult = source.submitEncodedField(stringFromNSString(fieldKey),
{stringFromNSString(fieldID), generation, dataVersion}, *contract, bytes);
result = static_cast<MLNParticleEncodedFieldSubmitResult>(coreResult);
return true;
}];
if (completed && result > MLNParticleEncodedFieldSubmitResultNone) {
[_submittedEncodedFieldKeys addObject:fieldKey];
[self refreshV4CadenceWake];
}
return completed ? result : MLNParticleEncodedFieldSubmitResultNone;
}
- (BOOL)setEncodedColorBindingWithFieldKey:(NSString *)fieldKey semanticID:(uint16_t)semanticID groupID:(uint16_t)groupID expectedValueClass:(MLNParticleFieldValueClass)expectedValueClass expectedUnitID:(uint16_t)expectedUnitID error:(NSError **)error {
if (!hasValidIdentifier(fieldKey) || semanticID == 0 || expectedValueClass < MLNParticleFieldValueClassScalar || expectedValueClass > MLNParticleFieldValueClassDirectionAngle) {
setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid encoded color binding."); return NO;
}
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) {
source.setColorBinding({stringFromNSString(fieldKey), {semanticID, groupID}, static_cast<mbgl::particle::FieldValueClass>(expectedValueClass), expectedUnitID});
return true;
}];
return completed;
}
- (BOOL)setEncodedParticleBindingWithFieldKey:(NSString *)fieldKey mode:(MLNParticleEncodedParticleBindingMode)mode uSemanticID:(uint16_t)uSemanticID uGroupID:(uint16_t)uGroupID vSemanticID:(uint16_t)vSemanticID vGroupID:(uint16_t)vGroupID directionSemanticID:(uint16_t)directionSemanticID directionGroupID:(uint16_t)directionGroupID expectedUnitID:(uint16_t)expectedUnitID error:(NSError **)error {
if (!hasValidIdentifier(fieldKey)) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid encoded particle field key."); return NO; }
const bool vector = mode == MLNParticleEncodedParticleBindingModeVectorComponents && uSemanticID != 0 && vSemanticID != 0 && directionSemanticID == 0 && directionGroupID == 0;
const bool direction = mode == MLNParticleEncodedParticleBindingModeDirectionAngle && uSemanticID == 0 && uGroupID == 0 && vSemanticID == 0 && vGroupID == 0 && directionSemanticID != 0;
if (!vector && !direction) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Inactive particle selectors must be exactly semanticID = 0 and groupID = 0."); return NO; }
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) {
mbgl::particle::ParticleBinding binding;
binding.fieldKey = stringFromNSString(fieldKey);
binding.expectedUnitID = expectedUnitID;
if (vector) { binding.mode = mbgl::particle::ParticleBindingMode::VectorComponents; binding.uPlane = {uSemanticID, uGroupID}; binding.vPlane = {vSemanticID, vGroupID}; }
else { binding.mode = mbgl::particle::ParticleBindingMode::DirectionAngle; binding.directionPlane = {directionSemanticID, directionGroupID}; }
source.setParticleBinding(std::move(binding));
return true;
}];
if (completed) { _v4ParticleBindingFieldKey = [fieldKey copy]; [self refreshV4CadenceWake]; }
return completed;
}
- (BOOL)setEncodedForecastTime:(uint64_t)timestamp error:(NSError **)error {
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { return source.setRequestedForecastTime(timestamp); }];
}
- (BOOL)setEncodedForecastPlaybackState:(BOOL)playing error:(NSError **)error {
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { source.setForecastPlaybackState(playing ? mbgl::particle::ForecastPlaybackState::Playing : mbgl::particle::ForecastPlaybackState::Paused); return true; }];
}
- (BOOL)setEncodedForecastPlaybackSpeed:(float)speed error:(NSError **)error {
if (!std::isfinite(speed) || speed <= 0) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Forecast playback speed must be finite and greater than zero."); return NO; }
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { return source.setForecastPlaybackSpeed(speed); }];
}
- (BOOL)clearEncodedFieldWithFieldKey:(NSString *)fieldKey error:(NSError **)error {
if (!hasValidIdentifier(fieldKey)) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid encoded field key."); return NO; }
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { return source.clearEncodedField(stringFromNSString(fieldKey)); }];
if (completed) { [_submittedEncodedFieldKeys removeObject:fieldKey]; [self refreshV4CadenceWake]; }
return completed;
}
- (BOOL)clearAllEncodedFieldsWithError:(NSError **)error {
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { source.clearAllEncodedFields(); return true; }];
if (completed) { [_submittedEncodedFieldKeys removeAllObjects]; [self refreshV4CadenceWake]; }
return completed;
}
- (MLNParticleHostFieldSubmitResult)submitWindFieldWithMetadataJSON:(NSString *)metadataJSON payload:(NSData *)payload error:(NSError **)error {
if (_state == MLNParticleFieldRuntimeStateDisabled) { setError(error, MLNParticleFieldRuntimeErrorDisposed, @"Particle field runtime is disabled; enable it before submitting fields."); return MLNParticleHostFieldSubmitResultNone; }
if (!metadataJSON.length || [metadataJSON lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > MaxMetadataUTF8Bytes || !payload.length || payload.length > MaxQbinBytes) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid V3 wind field payload."); return MLNParticleHostFieldSubmitResultNone; }
__block MLNParticleHostFieldSubmitResult result = MLNParticleHostFieldSubmitResultNone;
BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) {
std::vector<char> bytes = charsFromData(payload);
result = static_cast<MLNParticleHostFieldSubmitResult>(source.submitHostField(stringFromNSString(metadataJSON), bytes)); return true;
}];
if (completed && result > 0) _state = result == MLNParticleHostFieldSubmitResultAcceptedPendingInitialWindow ? MLNParticleFieldRuntimeStateWaitingForFields : (result == MLNParticleHostFieldSubmitResultDuplicate ? _state : MLNParticleFieldRuntimeStateReady);
else if (completed && result == MLNParticleHostFieldSubmitResultRejectedSourceMode) _state = MLNParticleFieldRuntimeStateError;
return completed ? result : MLNParticleHostFieldSubmitResultNone;
}
- (MLNParticleHostFieldSubmitResult)submitFieldWithMetadataJSON:(NSString *)metadataJSON scalar:(NSData *)scalar vector:(NSData *)vector validity:(NSData *)validity error:(NSError **)error {
if (_state == MLNParticleFieldRuntimeStateDisabled) { setError(error, MLNParticleFieldRuntimeErrorDisposed, @"Particle field runtime is disabled; enable it before submitting fields."); return MLNParticleHostFieldSubmitResultNone; }
const NSUInteger scalarLength = scalar.length;
const NSUInteger vectorLength = vector.length;
const NSUInteger validityLength = validity.length;
if (!metadataJSON.length || [metadataJSON lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > MaxMetadataUTF8Bytes || !validityLength || (!scalar && !vector) || (scalar && !scalarLength) || (vector && !vectorLength) || scalarLength > MaxQbinBytes || vectorLength > MaxQbinBytes || validityLength > MaxQbinBytes || vectorLength > MaxQbinBytes - scalarLength || validityLength > MaxQbinBytes - scalarLength - vectorLength) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid V3 field payload."); return MLNParticleHostFieldSubmitResultNone; }
__block MLNParticleHostFieldSubmitResult result = MLNParticleHostFieldSubmitResultNone;
BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) {
mbgl::particle::ParticleFieldPayloadBundle payloads;
if (scalar) payloads.scalar = charsFromData(scalar); if (vector) payloads.vector = charsFromData(vector); payloads.validity = charsFromData(validity);
result = static_cast<MLNParticleHostFieldSubmitResult>(source.submitHostField(stringFromNSString(metadataJSON), payloads)); return true;
}];
if (completed && result > 0) _state = result == MLNParticleHostFieldSubmitResultAcceptedPendingInitialWindow ? MLNParticleFieldRuntimeStateWaitingForFields : (result == MLNParticleHostFieldSubmitResultDuplicate ? _state : MLNParticleFieldRuntimeStateReady);
else if (completed && result == MLNParticleHostFieldSubmitResultRejectedSourceMode) _state = MLNParticleFieldRuntimeStateError;
return completed ? result : MLNParticleHostFieldSubmitResultNone;
}
- (MLNParticleFieldRuntimeCommandResult)clearFieldsWithError:(NSError **)error {
if (_disposed || _state == MLNParticleFieldRuntimeStateDisabled) return MLNParticleFieldRuntimeCommandResultRejected;
__block bool accepted = false; BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { accepted = source.clearHostFields(); return true; }];
if (completed && accepted) _state = MLNParticleFieldRuntimeStateWaitingForFields; return commandResult(completed && accepted);
}
- (MLNParticleFieldRuntimeCommandResult)setWeatherEnabled:(BOOL)enabled error:(NSError **)error {
if (_disposed) return MLNParticleFieldRuntimeCommandResultRejected;
if (enabled && _state == MLNParticleFieldRuntimeStateError) return MLNParticleFieldRuntimeCommandResultRejected;
if (enabled && _state != MLNParticleFieldRuntimeStateDisabled) return MLNParticleFieldRuntimeCommandResultAccepted;
__block bool accepted = false; BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &weather, mbgl::style::RasterParticleLayer &particle) {
if (!enabled) { source.controlRuntimeTimeline(1); weather.setVisibility(mbgl::style::VisibilityType::None); particle.setVisibility(mbgl::style::VisibilityType::None); accepted = source.clearHostFields(); }
else { weather.setVisibility(_weatherColorVisible ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None); particle.setVisibility(_particleVisible ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None); accepted = true; } return true;
}];
if (completed && accepted) _state = enabled ? MLNParticleFieldRuntimeStateWaitingForFields : MLNParticleFieldRuntimeStateDisabled; else if (completed) _state = MLNParticleFieldRuntimeStateError;
[self refreshV4CadenceWake];
return commandResult(completed && accepted);
}
- (MLNParticleFieldRuntimeCommandResult)setWeatherColorVisible:(BOOL)visible error:(NSError **)error { if (_disposed) return MLNParticleFieldRuntimeCommandResultRejected; _weatherColorVisible = visible; return [self setVisibility:visible weather:YES error:error]; }
- (MLNParticleFieldRuntimeCommandResult)setParticleVisible:(BOOL)visible error:(NSError **)error { if (_disposed) return MLNParticleFieldRuntimeCommandResultRejected; _particleVisible = visible; MLNParticleFieldRuntimeCommandResult result = [self setVisibility:visible weather:NO error:error]; [self refreshV4CadenceWake]; return result; }
- (MLNParticleFieldRuntimeCommandResult)setVisibility:(BOOL)visible weather:(BOOL)weatherLayer error:(NSError **)error {
BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &weather, mbgl::style::RasterParticleLayer &particle) { (weatherLayer ? static_cast<mbgl::style::Layer &>(weather) : static_cast<mbgl::style::Layer &>(particle)).setVisibility(visible && _state != MLNParticleFieldRuntimeStateDisabled ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None); return true; }]; return commandResult(completed);
}
- (MLNParticleFieldRuntimeCommandResult)setTargetFps:(MLNParticleFieldRuntimeTargetFps)target error:(NSError **)error { uint8_t value = target == MLNParticleFieldRuntimeTargetFps30 ? 30 : target == MLNParticleFieldRuntimeTargetFps60 ? 60 : target == MLNParticleFieldRuntimeTargetFpsAuto ? 0 : 255; if (value == 255) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"Invalid target FPS."); return MLNParticleFieldRuntimeCommandResultRejected; } __block bool ok = false; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { ok = source.setRuntimeTargetFps(value); return true; }]; if (done && ok) { _targetFps = target; [self refreshV4CadenceWake]; } return commandResult(done && ok); }
- (MLNParticleFieldRuntimeCommandResult)setForecastTime:(uint64_t)timestamp error:(NSError **)error { if (_state == MLNParticleFieldRuntimeStateDisabled) return MLNParticleFieldRuntimeCommandResultRejected; __block bool ok = false; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { ok = source.seekRuntimeTimelineToTimestamp(timestamp); return true; }]; return commandResult(done && ok); }
- (MLNParticleFieldRuntimeCommandResult)setPlaybackState:(MLNParticleFieldRuntimePlaybackState)playback error:(NSError **)error { if (_state == MLNParticleFieldRuntimeStateDisabled || (playback != MLNParticleFieldRuntimePlaybackStatePlay && playback != MLNParticleFieldRuntimePlaybackStatePause)) return MLNParticleFieldRuntimeCommandResultRejected; __block bool ok = false; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { ok = source.controlRuntimeTimeline(playback == MLNParticleFieldRuntimePlaybackStatePlay ? 0 : 1); return true; }]; if (done && ok) _playbackState = playback; return commandResult(done && ok); }
- (MLNParticleFieldRuntimeCommandResult)setPlaybackSpeed:(float)speed error:(NSError **)error { if (!validFloat(speed) || _state == MLNParticleFieldRuntimeStateDisabled) return MLNParticleFieldRuntimeCommandResultRejected; __block bool ok = false; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { ok = source.controlRuntimeTimeline(2, speed); return true; }]; return commandResult(done && ok); }
- (MLNParticleFieldRuntimeCommandResult)setFieldValueSource:(MLNParticleFieldValueSource)value error:(NSError **)error { if (_disposed || value > MLNParticleFieldValueSourceVectorMagnitude) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &weather, mbgl::style::RasterParticleLayer &) { weather.setFieldValueSource(static_cast<mbgl::style::FieldValueSource>(value)); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setWeatherColorStops:(NSArray<MLNParticleWeatherColorStop *> *)stops error:(NSError **)error {
if (_disposed || !stops) return MLNParticleFieldRuntimeCommandResultRejected;
std::vector<mbgl::style::WeatherColorStop> coreStops; coreStops.reserve(stops.count);
for (MLNParticleWeatherColorStop *stop in stops) coreStops.push_back({stop.value, {stop.red, stop.green, stop.blue, stop.alpha}});
__block bool ok = false; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &weather, mbgl::style::RasterParticleLayer &) { ok = weather.setColorStops(std::move(coreStops)); return true; }]; return commandResult(done && ok);
}
- (MLNParticleFieldRuntimeCommandResult)setWeatherColorOpacity:(float)value error:(NSError **)error { if (_disposed || !validFloat(value)) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &weather, mbgl::style::RasterParticleLayer &) { weather.setOpacity(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setParticleCount:(uint32_t)value error:(NSError **)error { if (_disposed) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setParticleCount(value); return particle.getParticleCount() == value; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setParticleLineWidth:(float)value error:(NSError **)error { if (_disposed || !validFloat(value)) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setLineWidth(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setParticleLength:(float)value error:(NSError **)error { if (_disposed || !validFloat(value)) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setParticleLength(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setParticleOpacity:(float)value error:(NSError **)error { if (_disposed || !validFloat(value)) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setParticleOpacity(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setTrailOpacity:(float)value error:(NSError **)error { if (_disposed || !validFloat(value)) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setTrailOpacity(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setTrailFade:(float)value error:(NSError **)error { if (_disposed || !validFloat(value)) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setTrailFade(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setParticleAdvectionScale:(float)value error:(NSError **)error { if (_disposed || !validFloat(value) || value <= 0) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setAdvectionScale(value); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setParticleColorRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha error:(NSError **)error { if (_disposed || !mbgl::style::validWeatherRGBA({red, green, blue, alpha})) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setParticleColor({red, green, blue, alpha}); return true; }]; return commandResult(done); }
- (MLNParticleFieldRuntimeCommandResult)setTrailColorRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha error:(NSError **)error { if (_disposed || !mbgl::style::validWeatherRGBA({red, green, blue, alpha})) return MLNParticleFieldRuntimeCommandResultRejected; BOOL done = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &particle) { particle.setTrailColor({red, green, blue, alpha}); return true; }]; return commandResult(done); }
@end
namespace { const void *ParticleFieldRuntimeAssociationKey = &ParticleFieldRuntimeAssociationKey; }
@implementation MLNMapView (ParticleFieldRuntime)
- (MLNParticleFieldRuntime *)replaceParticleFieldRuntimeWithConfiguration:(MLNParticleFieldRuntimeConfiguration *)configuration error:(NSError **)error {
[self disposeParticleFieldRuntime];
if (!configuration) { setError(error, MLNParticleFieldRuntimeErrorInvalidArgument, @"A particle field runtime configuration is required."); return nil; }
MLNParticleFieldRuntime *runtime = [[MLNParticleFieldRuntime alloc] initWithMapView:self configuration:configuration error:error];
if (!runtime) return nil;
objc_setAssociatedObject(self, ParticleFieldRuntimeAssociationKey, runtime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return runtime;
}
- (MLNParticleFieldRuntime *)particleFieldRuntime {
return objc_getAssociatedObject(self, ParticleFieldRuntimeAssociationKey);
}
- (void)disposeParticleFieldRuntime {
MLNParticleFieldRuntime *runtime = self.particleFieldRuntime;
[runtime dispose];
objc_setAssociatedObject(self, ParticleFieldRuntimeAssociationKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end