fix: audit STEP-057 weather runtime facade
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -1214,6 +1214,10 @@ jni::jboolean NativeMapView::setWeatherColorStops(JNIEnv& env,
|
||||
return layer->setColorStops(std::move(stops));
|
||||
}
|
||||
|
||||
jni::jint NativeMapView::getWeatherColorStopCapacity(JNIEnv&) {
|
||||
return static_cast<jni::jint>(mbgl::style::MaxWeatherColorStops);
|
||||
}
|
||||
|
||||
jni::jboolean NativeMapView::advanceRasterArraySourceFieldWindow(JNIEnv& env, const jni::String& sourceId) {
|
||||
const auto id = jni::Make<std::string>(env, sourceId);
|
||||
auto* source = map->getStyle().getSource(id);
|
||||
@@ -1692,6 +1696,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
|
||||
METHOD(&NativeMapView::setStyleLayerVisibility, "nativeSetStyleLayerVisibility"),
|
||||
METHOD(&NativeMapView::setRasterParticleColors, "nativeSetRasterParticleColors"),
|
||||
METHOD(&NativeMapView::setWeatherColorStops, "nativeSetWeatherColorStops"),
|
||||
METHOD(&NativeMapView::getWeatherColorStopCapacity, "nativeGetWeatherColorStopCapacity"),
|
||||
METHOD(&NativeMapView::advanceRasterArraySourceFieldWindow, "nativeAdvanceRasterArraySourceFieldWindow"),
|
||||
METHOD(&NativeMapView::repeatRasterArraySourceFieldWindow, "nativeRepeatRasterArraySourceFieldWindow"),
|
||||
METHOD(&NativeMapView::submitRasterArraySourceHostField, "nativeSubmitRasterArraySourceHostField"),
|
||||
|
||||
@@ -285,6 +285,7 @@ public:
|
||||
jni::jboolean setStyleLayerVisibility(JNIEnv&, const jni::String&, jni::jboolean);
|
||||
jni::jboolean setRasterParticleColors(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&, const jni::Array<jni::jfloat>&);
|
||||
jni::jboolean setWeatherColorStops(JNIEnv&, const jni::String&, const jni::Array<jni::jfloat>&, const jni::Array<jni::jfloat>&);
|
||||
jni::jint getWeatherColorStopCapacity(JNIEnv&);
|
||||
jni::jboolean advanceRasterArraySourceFieldWindow(JNIEnv&, const jni::String&);
|
||||
jni::jboolean repeatRasterArraySourceFieldWindow(JNIEnv&, const jni::String&);
|
||||
jni::jint submitRasterArraySourceHostField(JNIEnv&,
|
||||
|
||||
@@ -306,6 +306,11 @@ public final class MapLibreMap {
|
||||
return nativeMapView.setWeatherColorStops(layerId, speedsMps, rgbaStops);
|
||||
}
|
||||
|
||||
/** Returns the Core-owned WeatherColor stop capacity for typed Host validation. */
|
||||
public int getWeatherColorStopCapacity() {
|
||||
return nativeMapView.getWeatherColorStopCapacity();
|
||||
}
|
||||
|
||||
/** Advances a Source-owned raster-array runtime Field window without reloading the Style. */
|
||||
public boolean advanceRasterArraySourceFieldWindow(String sourceId) {
|
||||
return nativeMapView.advanceRasterArraySourceFieldWindow(sourceId);
|
||||
|
||||
@@ -147,6 +147,8 @@ interface NativeMap {
|
||||
|
||||
boolean setWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops);
|
||||
|
||||
int getWeatherColorStopCapacity();
|
||||
|
||||
boolean advanceRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
boolean repeatRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
@@ -968,6 +968,10 @@ final class NativeMapView implements NativeMap {
|
||||
return !checkState("setWeatherColorStops") && nativeSetWeatherColorStops(layerId, speedsMps, rgbaStops);
|
||||
}
|
||||
|
||||
public int getWeatherColorStopCapacity() {
|
||||
return checkState("getWeatherColorStopCapacity") ? 0 : nativeGetWeatherColorStopCapacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advanceRasterArraySourceFieldWindow(String sourceId) {
|
||||
return !checkState("advanceRasterArraySourceFieldWindow")
|
||||
@@ -1742,6 +1746,7 @@ final class NativeMapView implements NativeMap {
|
||||
@Keep private native boolean nativeSetStyleLayerVisibility(String layerId, boolean visible);
|
||||
@Keep private native boolean nativeSetRasterParticleColors(String layerId, float[] particleColor, float[] trailColor);
|
||||
@Keep private native boolean nativeSetWeatherColorStops(String layerId, float[] speedsMps, float[] rgbaStops);
|
||||
@Keep private native int nativeGetWeatherColorStopCapacity();
|
||||
|
||||
@Keep
|
||||
private native boolean nativeAdvanceRasterArraySourceFieldWindow(String sourceId);
|
||||
|
||||
@@ -40,35 +40,41 @@ public final class WeatherRuntime {
|
||||
|
||||
private final MapLibreMap map;
|
||||
private final Config config;
|
||||
private final int weatherColorStopCapacity;
|
||||
private State state = State.WAITING_FOR_FIELDS;
|
||||
private boolean weatherColorVisible = true;
|
||||
private boolean particleVisible = true;
|
||||
|
||||
public WeatherRuntime(@NonNull MapLibreMap map, @NonNull Config config) { this.map = map; this.config = config; }
|
||||
public WeatherRuntime(@NonNull MapLibreMap map, @NonNull Config config) {
|
||||
this.map = map; this.config = config; this.weatherColorStopCapacity = map.getWeatherColorStopCapacity();
|
||||
}
|
||||
public State getState() { return state; }
|
||||
public Config getConfig() { return config; }
|
||||
/** Core-owned capacity; Host never sees the shader's UBO layout. */
|
||||
public int getWeatherColorStopCapacity() { return weatherColorStopCapacity; }
|
||||
|
||||
public FieldSubmitResult submitWindField(@NonNull String metadataJson, @NonNull byte[] payloadBytes) {
|
||||
requireEnabled();
|
||||
if (metadataJson.isEmpty() || payloadBytes.length == 0) { state = State.ERROR; return FieldSubmitResult.REJECTED_ADAPTER; }
|
||||
FieldSubmitResult result = FieldSubmitResult.fromCode(map.submitRasterArraySourceHostField(config.sourceId, metadataJson, payloadBytes));
|
||||
if (result == FieldSubmitResult.INITIAL_WINDOW_READY || result == FieldSubmitResult.ACCEPTED_NEXT_SNAPSHOT || result == FieldSubmitResult.DUPLICATE) state = State.READY;
|
||||
else if (!result.accepted()) state = State.ERROR;
|
||||
state = stateAfterSubmit(state, result);
|
||||
return result;
|
||||
}
|
||||
public CommandResult clearWindFields() { if (state == State.DISABLED) return CommandResult.REJECTED; boolean ok = map.clearRasterArraySourceHostFields(config.sourceId); if (ok) state = State.WAITING_FOR_FIELDS; return result(ok); }
|
||||
public CommandResult clearWindFields() { if (state == State.DISABLED) return CommandResult.REJECTED; boolean ok = map.clearRasterArraySourceHostFields(config.sourceId); state = stateAfterClear(state, ok); return result(ok); }
|
||||
public CommandResult setWeatherEnabled(boolean enabled) {
|
||||
if (!enabled) {
|
||||
if (state == State.DISABLED) return CommandResult.ACCEPTED;
|
||||
map.controlRasterArraySourceTimeline(config.sourceId, 1, 0f);
|
||||
boolean color = map.setStyleLayerVisibility(config.weatherColorLayerId, false);
|
||||
boolean particle = map.setStyleLayerVisibility(config.particleLayerId, false);
|
||||
boolean clear = map.clearRasterArraySourceHostFields(config.sourceId);
|
||||
state = (color && particle && clear) ? State.DISABLED : State.ERROR;
|
||||
state = stateAfterDisable(color && particle && clear);
|
||||
return result(color && particle && clear);
|
||||
}
|
||||
if (state != State.DISABLED) return CommandResult.ACCEPTED;
|
||||
boolean color = map.setStyleLayerVisibility(config.weatherColorLayerId, weatherColorVisible);
|
||||
boolean particle = map.setStyleLayerVisibility(config.particleLayerId, particleVisible);
|
||||
if (color && particle) state = State.WAITING_FOR_FIELDS; else state = State.ERROR;
|
||||
state = stateAfterEnable(state, color && particle);
|
||||
return result(color && particle);
|
||||
}
|
||||
public CommandResult setWeatherColorVisible(boolean visible) { weatherColorVisible = visible; return result(map.setStyleLayerVisibility(config.weatherColorLayerId, visible && state != State.DISABLED)); }
|
||||
@@ -77,16 +83,35 @@ public final class WeatherRuntime {
|
||||
public CommandResult setPlaybackState(@NonNull PlaybackState playback) { requireEnabled(); return result(map.controlRasterArraySourceTimeline(config.sourceId, playback == PlaybackState.PLAY ? 0 : 1, 0f)); }
|
||||
public CommandResult setPlaybackSpeed(@NonNull PlaybackSpeed speed) { requireEnabled(); return result(map.controlRasterArraySourceTimeline(config.sourceId, 2, speed.value)); }
|
||||
public CommandResult setWeatherColorOpacity(float opacity) { return result(Float.isFinite(opacity) && map.setStyleLayerFloatProperty(config.weatherColorLayerId, "weather-color-opacity", opacity)); }
|
||||
public CommandResult setParticleVisual(String property, float value) { return result(Float.isFinite(value) && map.setStyleLayerFloatProperty(config.particleLayerId, property, value)); }
|
||||
public CommandResult setParticleCount(int count) { return setParticleFloat("raster-particle-particle-count", count); }
|
||||
public CommandResult setParticleLineWidth(float width) { return setParticleFloat("raster-particle-line-width", width); }
|
||||
public CommandResult setParticleLength(float length) { return setParticleFloat("raster-particle-particle-length", length); }
|
||||
public CommandResult setParticleOpacity(float opacity) { return setParticleFloat("raster-particle-particle-opacity", opacity); }
|
||||
public CommandResult setTrailOpacity(float opacity) { return setParticleFloat("raster-particle-trail-opacity", opacity); }
|
||||
public CommandResult setTrailFade(float fade) { return setParticleFloat("raster-particle-trail-fade", fade); }
|
||||
public CommandResult setParticleSpeedColorEnabled(boolean enabled) { return setParticleFloat("raster-particle-speed-color-enabled", enabled ? 1f : 0f); }
|
||||
public CommandResult setParticleColors(float red, float green, float blue, float alpha, float trailRed, float trailGreen, float trailBlue, float trailAlpha) {
|
||||
return result(map.setRasterParticleColors(config.particleLayerId, new float[]{red, green, blue, alpha}, new float[]{trailRed, trailGreen, trailBlue, trailAlpha}));
|
||||
}
|
||||
public CommandResult setWeatherColorStops(@NonNull List<ColorStop> stops) {
|
||||
if (stops.size() < 2 || stops.size() > 8) return CommandResult.REJECTED;
|
||||
if (weatherColorStopCapacity < 2 || stops.size() < 2 || stops.size() > weatherColorStopCapacity) return CommandResult.REJECTED;
|
||||
float[] speeds = new float[stops.size()]; float[] colors = new float[stops.size() * 4];
|
||||
for (int i = 0; i < stops.size(); i++) { ColorStop stop = stops.get(i); speeds[i] = stop.speedMps; colors[i * 4] = stop.red; colors[i * 4 + 1] = stop.green; colors[i * 4 + 2] = stop.blue; colors[i * 4 + 3] = stop.alpha; }
|
||||
return result(map.setWeatherColorStops(config.weatherColorLayerId, speeds, colors));
|
||||
}
|
||||
private static CommandResult result(boolean accepted) { return accepted ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
|
||||
private CommandResult setParticleFloat(String property, float value) { return result(Float.isFinite(value) && map.setStyleLayerFloatProperty(config.particleLayerId, property, value)); }
|
||||
static State stateAfterSubmit(State current, FieldSubmitResult submit) {
|
||||
if (submit == FieldSubmitResult.ACCEPTED_PENDING_INITIAL_WINDOW) return State.WAITING_FOR_FIELDS;
|
||||
if (submit == FieldSubmitResult.INITIAL_WINDOW_READY || submit == FieldSubmitResult.ACCEPTED_NEXT_SNAPSHOT) return State.READY;
|
||||
if (submit == FieldSubmitResult.DUPLICATE) return current;
|
||||
return State.ERROR;
|
||||
}
|
||||
static State stateAfterClear(State current, boolean accepted) { return accepted ? State.WAITING_FOR_FIELDS : current; }
|
||||
static State stateAfterDisable(boolean accepted) { return accepted ? State.DISABLED : State.ERROR; }
|
||||
static State stateAfterEnable(State current, boolean accepted) {
|
||||
if (!accepted) return State.ERROR;
|
||||
return current == State.DISABLED ? State.WAITING_FOR_FIELDS : current;
|
||||
}
|
||||
private void requireEnabled() { if (state == State.DISABLED) throw new IllegalStateException("WeatherRuntime is disabled; enable and submit fields first"); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.maplibre.android.maps;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/** Unit coverage for facade-only state transitions; no renderer frame callback is involved. */
|
||||
public class WeatherRuntimeStateTest {
|
||||
@Test public void enableIsIdempotentAndDisabledEnableWaitsForFields() {
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterEnable(WeatherRuntime.State.READY, true));
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterEnable(WeatherRuntime.State.DISABLED, true));
|
||||
}
|
||||
@Test public void submitRecoversErrorAndReachesReadyOnlyWithWindow() {
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.ERROR, WeatherRuntime.FieldSubmitResult.ACCEPTED_PENDING_INITIAL_WINDOW));
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.FieldSubmitResult.INITIAL_WINDOW_READY));
|
||||
assertEquals(WeatherRuntime.State.READY, WeatherRuntime.stateAfterSubmit(WeatherRuntime.State.READY, WeatherRuntime.FieldSubmitResult.DUPLICATE));
|
||||
}
|
||||
@Test public void clearAndDisableUseSuccessfulCoreResult() {
|
||||
assertEquals(WeatherRuntime.State.WAITING_FOR_FIELDS, WeatherRuntime.stateAfterClear(WeatherRuntime.State.READY, true));
|
||||
assertEquals(WeatherRuntime.State.DISABLED, WeatherRuntime.stateAfterDisable(true));
|
||||
assertEquals(WeatherRuntime.State.ERROR, WeatherRuntime.stateAfterDisable(false));
|
||||
}
|
||||
}
|
||||
@@ -534,8 +534,20 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setParticleFloat(property: String, value: Float): Boolean {
|
||||
return weatherRuntime?.setParticleVisual(property, value) == WeatherRuntime.CommandResult.ACCEPTED ||
|
||||
(weatherRuntime == null && maplibreMap.setStyleLayerFloatProperty(PARTICLE_LAYER_ID, property, value))
|
||||
val runtimeResult = weatherRuntime?.let { runtime ->
|
||||
when (property) {
|
||||
"raster-particle-particle-count" -> runtime.setParticleCount(value.toInt())
|
||||
"raster-particle-line-width" -> runtime.setParticleLineWidth(value)
|
||||
"raster-particle-particle-length" -> runtime.setParticleLength(value)
|
||||
"raster-particle-particle-opacity" -> runtime.setParticleOpacity(value)
|
||||
"raster-particle-trail-opacity" -> runtime.setTrailOpacity(value)
|
||||
"raster-particle-trail-fade" -> runtime.setTrailFade(value)
|
||||
"raster-particle-speed-color-enabled" -> runtime.setParticleSpeedColorEnabled(value > 0.5f)
|
||||
else -> null // Test-only direct entry retains fixture diagnostics such as time-mix.
|
||||
}
|
||||
}
|
||||
return runtimeResult == WeatherRuntime.CommandResult.ACCEPTED ||
|
||||
(runtimeResult == null && maplibreMap.setStyleLayerFloatProperty(PARTICLE_LAYER_ID, property, value))
|
||||
}
|
||||
|
||||
private fun resetVisualDefaults() {
|
||||
|
||||
Reference in New Issue
Block a user