feat: complete STEP-078 V4 bridge
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-09-13 16:10:49 +08:00
parent 4b5192e224
commit 73779b89bc
18 changed files with 403 additions and 2 deletions

View File

@@ -36,6 +36,7 @@
#include <mbgl/style/image.hpp>
#include <mbgl/style/filter.hpp>
#include <mbgl/renderer/query.hpp>
#include <mbgl/particle/field/field_profile_contract_json.hpp>
// Java -> C++ conversion
#include "style/android_conversion.hpp"
@@ -68,6 +69,8 @@
namespace mbgl {
namespace android {
namespace {}
NativeMapView::NativeMapView(jni::JNIEnv& _env,
const jni::Object<NativeMapView>& _obj,
const jni::Object<FileSource>& jFileSource,
@@ -1419,6 +1422,24 @@ jni::jboolean NativeMapView::controlRasterArraySourceTimeline(JNIEnv& env,
return success;
}
jni::jint NativeMapView::submitRasterArraySourceEncodedField(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, const jni::String& fieldId, jni::jlong generation, jni::jlong dataVersion, const jni::String& contractJson, const jni::Array<jbyte>& qbin) {
const auto id=jni::Make<std::string>(env,sourceId); auto* source=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(id));
if(!source) return 0; // bridge-layer SOURCE_MISSING sentinel, never a Core result
if(generation<=0 || dataVersion<0 || !qbin) return static_cast<jni::jint>(mbgl::style::RasterArrayEncodedFieldSubmitResult::RejectedDecode);
const auto contract=mbgl::particle::parseResolvedFieldProfileContractJson(jni::Make<std::string>(env,contractJson));
if(!contract) return static_cast<jni::jint>(mbgl::style::RasterArrayEncodedFieldSubmitResult::RejectedContract);
std::vector<uint8_t> bytes(qbin.Length(env)); if(!bytes.empty()) jni::GetArrayRegion(env,*qbin,0,bytes.size(),reinterpret_cast<jbyte*>(bytes.data()));
const auto result=source->submitEncodedField(jni::Make<std::string>(env,fieldKey), {jni::Make<std::string>(env,fieldId),static_cast<uint64_t>(generation),static_cast<uint64_t>(dataVersion)}, *contract, bytes);
return static_cast<jni::jint>(result);
}
jni::jint NativeMapView::clearRasterArraySourceEncodedField(JNIEnv& env,const jni::String& sourceId,const jni::String& fieldKey) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; return s->clearEncodedField(jni::Make<std::string>(env,fieldKey)) ? 1 : -1; }
jni::jint NativeMapView::clearAllRasterArraySourceEncodedFields(JNIEnv& env,const jni::String& sourceId) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; s->clearAllEncodedFields(); return 1; }
jni::jint NativeMapView::setRasterArraySourceEncodedColorBinding(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, jni::jint semanticID, jni::jint groupID, jni::jint valueClass, jni::jint unitID) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; if(semanticID<=0||semanticID>UINT16_MAX||groupID<0||groupID>UINT16_MAX||unitID<0||unitID>UINT16_MAX||(valueClass!=0&&valueClass!=2))return -1; s->setColorBinding({jni::Make<std::string>(env,fieldKey),{static_cast<uint16_t>(semanticID),static_cast<uint16_t>(groupID)},static_cast<mbgl::particle::FieldValueClass>(valueClass),static_cast<uint16_t>(unitID)}); return 1; }
jni::jint NativeMapView::setRasterArraySourceEncodedParticleBinding(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, jni::jint mode, jni::jint uSemanticID, jni::jint uGroupID, jni::jint vSemanticID, jni::jint vGroupID, jni::jint directionSemanticID, jni::jint directionGroupID, jni::jint unitID) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; const auto valid=[](jni::jint semantic,jni::jint group){return semantic>0&&semantic<=UINT16_MAX&&group>=0&&group<=UINT16_MAX;}; const auto inactive=[](jni::jint semantic,jni::jint group){return semantic==0&&group==0;}; if(unitID<0||unitID>UINT16_MAX)return -1; mbgl::particle::ParticleBinding binding; binding.fieldKey=jni::Make<std::string>(env,fieldKey); binding.expectedUnitID=static_cast<uint16_t>(unitID); if(mode==0&&valid(uSemanticID,uGroupID)&&valid(vSemanticID,vGroupID)&&inactive(directionSemanticID,directionGroupID)){binding.mode=mbgl::particle::ParticleBindingMode::VectorComponents;binding.uPlane={static_cast<uint16_t>(uSemanticID),static_cast<uint16_t>(uGroupID)};binding.vPlane={static_cast<uint16_t>(vSemanticID),static_cast<uint16_t>(vGroupID)};} else if(mode==1&&inactive(uSemanticID,uGroupID)&&inactive(vSemanticID,vGroupID)&&valid(directionSemanticID,directionGroupID)){binding.mode=mbgl::particle::ParticleBindingMode::DirectionAngle;binding.directionPlane={static_cast<uint16_t>(directionSemanticID),static_cast<uint16_t>(directionGroupID)};} else return -1; s->setParticleBinding(std::move(binding)); return 1; }
jni::jint NativeMapView::setRasterArraySourceEncodedForecastTime(JNIEnv& env,const jni::String& sourceId,jni::jlong timestamp) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; return timestamp>=0&&s->setRequestedForecastTime(static_cast<uint64_t>(timestamp))?1:-1; }
jni::jint NativeMapView::setRasterArraySourceEncodedForecastPlaybackState(JNIEnv& env,const jni::String& sourceId,jni::jboolean playing) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; s->setForecastPlaybackState(playing ? mbgl::particle::ForecastPlaybackState::Playing : mbgl::particle::ForecastPlaybackState::Paused); return 1; }
jni::jint NativeMapView::setRasterArraySourceEncodedForecastPlaybackSpeed(JNIEnv& env,const jni::String& sourceId,jni::jfloat speed) { auto* s=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(jni::Make<std::string>(env,sourceId))); if(!s)return 0; return s->setForecastPlaybackSpeed(speed)?1:-1; }
void NativeMapView::addLayer(JNIEnv& env, jlong nativeLayerPtr, const jni::String& before) {
assert(nativeLayerPtr != 0);
@@ -1824,6 +1845,14 @@ void NativeMapView::registerNative(jni::JNIEnv& env) {
METHOD(&NativeMapView::clearRasterArraySourceHostFields, "nativeClearRasterArraySourceHostFields"),
METHOD(&NativeMapView::seekRasterArraySourceTimelineToTimestamp, "nativeSeekRasterArraySourceTimelineToTimestamp"),
METHOD(&NativeMapView::controlRasterArraySourceTimeline, "nativeControlRasterArraySourceTimeline"),
METHOD(&NativeMapView::submitRasterArraySourceEncodedField, "nativeSubmitRasterArraySourceEncodedField"),
METHOD(&NativeMapView::clearRasterArraySourceEncodedField, "nativeClearRasterArraySourceEncodedField"),
METHOD(&NativeMapView::clearAllRasterArraySourceEncodedFields, "nativeClearAllRasterArraySourceEncodedFields"),
METHOD(&NativeMapView::setRasterArraySourceEncodedColorBinding, "nativeSetRasterArraySourceEncodedColorBinding"),
METHOD(&NativeMapView::setRasterArraySourceEncodedParticleBinding, "nativeSetRasterArraySourceEncodedParticleBinding"),
METHOD(&NativeMapView::setRasterArraySourceEncodedForecastTime, "nativeSetRasterArraySourceEncodedForecastTime"),
METHOD(&NativeMapView::setRasterArraySourceEncodedForecastPlaybackState, "nativeSetRasterArraySourceEncodedForecastPlaybackState"),
METHOD(&NativeMapView::setRasterArraySourceEncodedForecastPlaybackSpeed, "nativeSetRasterArraySourceEncodedForecastPlaybackSpeed"),
METHOD(&NativeMapView::addLayer, "nativeAddLayer"),
METHOD(&NativeMapView::addLayerAbove, "nativeAddLayerAbove"),
METHOD(&NativeMapView::addLayerAt, "nativeAddLayerAt"),

View File

@@ -310,6 +310,14 @@ public:
jni::jboolean clearRasterArraySourceHostFields(JNIEnv&, const jni::String&);
jni::jboolean seekRasterArraySourceTimelineToTimestamp(JNIEnv&, const jni::String&, jni::jlong);
jni::jboolean controlRasterArraySourceTimeline(JNIEnv&, const jni::String&, jni::jint, jni::jfloat);
jni::jint submitRasterArraySourceEncodedField(JNIEnv&, const jni::String&, const jni::String&, const jni::String&, jni::jlong, jni::jlong, const jni::String&, const jni::Array<jbyte>&);
jni::jint clearRasterArraySourceEncodedField(JNIEnv&, const jni::String&, const jni::String&);
jni::jint clearAllRasterArraySourceEncodedFields(JNIEnv&, const jni::String&);
jni::jint setRasterArraySourceEncodedColorBinding(JNIEnv&, const jni::String&, const jni::String&, jni::jint, jni::jint, jni::jint, jni::jint);
jni::jint setRasterArraySourceEncodedParticleBinding(JNIEnv&, const jni::String&, const jni::String&, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint, jni::jint);
jni::jint setRasterArraySourceEncodedForecastTime(JNIEnv&, const jni::String&, jni::jlong);
jni::jint setRasterArraySourceEncodedForecastPlaybackState(JNIEnv&, const jni::String&, jni::jboolean);
jni::jint setRasterArraySourceEncodedForecastPlaybackSpeed(JNIEnv&, const jni::String&, jni::jfloat);
void addLayer(JNIEnv&, jlong, const jni::String&);

View File

@@ -425,6 +425,16 @@ public final class MapLibreMap {
return nativeMapView.controlRasterArraySourceTimeline(sourceId, command, value);
}
/** V4-only QBIN ingress. It never enters the V3 runtime timeline. */
public int submitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin) { return nativeMapView.submitRasterArraySourceEncodedField(sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin); }
public int clearRasterArraySourceEncodedField(String sourceId, String fieldKey) { return nativeMapView.clearRasterArraySourceEncodedField(sourceId, fieldKey); }
public int clearAllRasterArraySourceEncodedFields(String sourceId) { return nativeMapView.clearAllRasterArraySourceEncodedFields(sourceId); }
public int setRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID) { return nativeMapView.setRasterArraySourceEncodedColorBinding(sourceId, fieldKey, semanticID, groupID, valueClass, unitID); }
public int setRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID) { return nativeMapView.setRasterArraySourceEncodedParticleBinding(sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, unitID); }
public int setRasterArraySourceEncodedForecastTime(String sourceId, long timestamp) { return nativeMapView.setRasterArraySourceEncodedForecastTime(sourceId, timestamp); }
public int setRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing) { return nativeMapView.setRasterArraySourceEncodedForecastPlaybackState(sourceId, playing); }
public int setRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed) { return nativeMapView.setRasterArraySourceEncodedForecastPlaybackSpeed(sourceId, speed); }
public static final int HOST_FIELD_REJECTED_SOURCE_MODE = -1;
public static final int HOST_FIELD_REJECTED_ADAPTER = -2;
public static final int HOST_FIELD_REJECTED_INCOMPATIBLE = -3;

View File

@@ -177,6 +177,15 @@ interface NativeMap {
boolean controlRasterArraySourceTimeline(String sourceId, int command, float value);
int submitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin);
int clearRasterArraySourceEncodedField(String sourceId, String fieldKey);
int clearAllRasterArraySourceEncodedFields(String sourceId);
int setRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID);
int setRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID);
int setRasterArraySourceEncodedForecastTime(String sourceId, long timestamp);
int setRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing);
int setRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed);
boolean removeLayer(@NonNull String layerId);
boolean removeLayer(@NonNull Layer layer);

View File

@@ -1047,6 +1047,15 @@ final class NativeMapView implements NativeMap {
&& nativeControlRasterArraySourceTimeline(sourceId, command, value);
}
@Override public int submitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin) { return checkState("submitRasterArraySourceEncodedField") ? 0 : nativeSubmitRasterArraySourceEncodedField(sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin); }
@Override public int clearRasterArraySourceEncodedField(String sourceId, String fieldKey) { return checkState("clearRasterArraySourceEncodedField") ? 0 : nativeClearRasterArraySourceEncodedField(sourceId, fieldKey); }
@Override public int clearAllRasterArraySourceEncodedFields(String sourceId) { return checkState("clearAllRasterArraySourceEncodedFields") ? 0 : nativeClearAllRasterArraySourceEncodedFields(sourceId); }
@Override public int setRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID) { return checkState("setRasterArraySourceEncodedColorBinding") ? 0 : nativeSetRasterArraySourceEncodedColorBinding(sourceId, fieldKey, semanticID, groupID, valueClass, unitID); }
@Override public int setRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID) { return checkState("setRasterArraySourceEncodedParticleBinding") ? 0 : nativeSetRasterArraySourceEncodedParticleBinding(sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, unitID); }
@Override public int setRasterArraySourceEncodedForecastTime(String sourceId, long timestamp) { return checkState("setRasterArraySourceEncodedForecastTime") ? 0 : nativeSetRasterArraySourceEncodedForecastTime(sourceId, timestamp); }
@Override public int setRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing) { return checkState("setRasterArraySourceEncodedForecastPlaybackState") ? 0 : nativeSetRasterArraySourceEncodedForecastPlaybackState(sourceId, playing); }
@Override public int setRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed) { return checkState("setRasterArraySourceEncodedForecastPlaybackSpeed") ? 0 : nativeSetRasterArraySourceEncodedForecastPlaybackSpeed(sourceId, speed); }
@Override
public void addLayer(@NonNull Layer layer) {
if (checkState("addLayer")) {
@@ -1822,6 +1831,14 @@ final class NativeMapView implements NativeMap {
@Keep
private native boolean nativeControlRasterArraySourceTimeline(String sourceId, int command, float value);
@Keep private native int nativeSubmitRasterArraySourceEncodedField(String sourceId, String fieldKey, String fieldId, long generation, long dataVersion, String contractJson, byte[] qbin);
@Keep private native int nativeClearRasterArraySourceEncodedField(String sourceId, String fieldKey);
@Keep private native int nativeClearAllRasterArraySourceEncodedFields(String sourceId);
@Keep private native int nativeSetRasterArraySourceEncodedColorBinding(String sourceId, String fieldKey, int semanticID, int groupID, int valueClass, int unitID);
@Keep private native int nativeSetRasterArraySourceEncodedParticleBinding(String sourceId, String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int unitID);
@Keep private native int nativeSetRasterArraySourceEncodedForecastTime(String sourceId, long timestamp);
@Keep private native int nativeSetRasterArraySourceEncodedForecastPlaybackState(String sourceId, boolean playing);
@Keep private native int nativeSetRasterArraySourceEncodedForecastPlaybackSpeed(String sourceId, float speed);
@Keep
private native void nativeAddLayer(long layerPtr, String before) throws CannotAddLayerException;

View File

@@ -27,6 +27,11 @@ public final class WeatherRuntime {
public boolean accepted() { return code > 0; }
}
public enum CommandResult { ACCEPTED, REJECTED }
public enum EncodedFieldSubmitResult {
ACCEPTED_INSERTED(1), ACCEPTED_REPLACED_REVISION(2), DUPLICATE(3), REJECTED_DECODE(-1), REJECTED_CONTRACT(-2), REJECTED_INVALID_FIELD_KEY(-3), REJECTED_IDENTITY_CONFLICT(-4), REJECTED_STALE_REVISION(-5), REJECTED_INCOMPATIBLE(-6), REJECTED_CAPACITY(-7);
final int code; EncodedFieldSubmitResult(int code) { this.code = code; }
static EncodedFieldSubmitResult fromCode(int code) { for (EncodedFieldSubmitResult v : values()) if (v.code == code) return v; throw new IllegalArgumentException("unknown encoded submit code"); }
}
public static final class Config {
public final String sourceId;
@@ -78,6 +83,28 @@ public final class WeatherRuntime {
if (state == State.READY && result.accepted()) scheduleCadenceWake();
return result;
}
/** Isolated V4 ingress: it deliberately does not mutate V3 state/cadence/timeline. */
public EncodedFieldSubmitResult submitEncodedField(@NonNull String fieldKey, @NonNull String fieldId, long generation, long dataVersion, @NonNull String contractJson, @NonNull byte[] qbin) {
requireAttached(); requireEnabled();
if (fieldKey.isEmpty() || fieldId.isEmpty() || generation <= 0 || dataVersion < 0 || contractJson.isEmpty() || qbin.length == 0) throw new IllegalArgumentException("invalid encoded field argument");
int code = map.submitRasterArraySourceEncodedField(config.sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin);
if (code == 0) throw new IllegalStateException("SOURCE_MISSING");
return EncodedFieldSubmitResult.fromCode(code);
}
public CommandResult clearEncodedField(@NonNull String fieldKey) { requireAttached(); return fieldKey.isEmpty() ? CommandResult.REJECTED : encodedCommand(map.clearRasterArraySourceEncodedField(config.sourceId, fieldKey)); }
public CommandResult clearAllEncodedFields() { requireAttached(); return encodedCommand(map.clearAllRasterArraySourceEncodedFields(config.sourceId)); }
/** V4 binding intent; unit and all mode selectors reach Core unchanged. */
public CommandResult setEncodedColorBinding(@NonNull String fieldKey, int semanticID, int groupID, int valueClass, int expectedUnitID) {
requireAttached(); return encodedCommand(map.setRasterArraySourceEncodedColorBinding(config.sourceId, fieldKey, semanticID, groupID, valueClass, expectedUnitID));
}
/** ABI reserves all selector slots: inactive slots must be exactly (0, 0). */
public CommandResult setEncodedParticleBinding(@NonNull String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int expectedUnitID) {
requireAttached(); return encodedCommand(map.setRasterArraySourceEncodedParticleBinding(config.sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, expectedUnitID));
}
public CommandResult setEncodedForecastTime(long timestamp) { requireAttached(); return timestamp < 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastTime(config.sourceId, timestamp)); }
/** State and speed intentionally remain independent V4 Core commands. */
public CommandResult setEncodedForecastPlaybackState(boolean playing) { requireAttached(); return encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackState(config.sourceId, playing)); }
public CommandResult setEncodedForecastPlaybackSpeed(float speed) { requireAttached(); return !Float.isFinite(speed) || speed <= 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackSpeed(config.sourceId, speed)); }
/** v2 provider-neutral ingress; missing scalar/vector is expressed only by its explicit presence flag. */
public FieldSubmitResult submitField(@NonNull String metadataJson, @Nullable byte[] scalarBytes, @Nullable byte[] vectorBytes, @NonNull byte[] validityBytes) {
requireAttached(); requireEnabled();
@@ -170,6 +197,8 @@ public final class WeatherRuntime {
return result(map.setWeatherColorStops(config.weatherColorLayerId, speeds, colors));
}
private static CommandResult result(boolean accepted) { return accepted ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
/** JNI 0 is private source/type lifecycle sentinel, never a Core V4 result. */
private static CommandResult encodedCommand(int code) { if (code == 0) throw new IllegalStateException("SOURCE_MISSING"); return code > 0 ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
public enum FieldValueSource { SCALAR(0f), VECTOR_MAGNITUDE(1f); final float code; FieldValueSource(float code) { this.code = code; } }
private boolean shouldWakeWeather() {
return !disposed && targetFps != TargetFps.AUTO && state == State.READY && (particleVisible || (weatherColorVisible && playbackState == PlaybackState.PLAY));