载入中...
搜索中...
未找到
ContainerAdapters.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <charconv>
7#include <exception>
8#include <limits>
9#include <string_view>
10#include <unordered_set>
11#include <utility>
12#include <vector>
13
14namespace eve::vehicle {
15namespace {
16
17constexpr std::string_view kOccupantPrefix = "vehicle:occupant:";
18
19[[nodiscard]] VehicleEntity::Seats* seatsOf(VehicleEntity* value) noexcept {
20 return value == nullptr ? nullptr : &*value->seats();
21}
22
23[[nodiscard]] const VehicleEntity::Seats* seatsOf(const VehicleEntity* value) noexcept {
24 return value == nullptr ? nullptr : &*const_cast<VehicleEntity*>(value)->seats();
25}
26
27[[nodiscard]] std::string quote(std::string_view value) {
28 std::string result;
29 result.reserve(value.size() + 2);
30 result.push_back('"');
31 for (const unsigned char byte : value) {
32 switch (byte) {
33 case '"': result += "\\\""; break;
34 case '\\': result += "\\\\"; break;
35 case '\n': result += "\\n"; break;
36 case '\r': result += "\\r"; break;
37 case '\t': result += "\\t"; break;
38 default:
39 if (byte < 0x20u) {
40 result += "\\u00";
41 constexpr char digits[] = "0123456789abcdef";
42 result.push_back(digits[(byte >> 4u) & 0x0fu]);
43 result.push_back(digits[byte & 0x0fu]);
44 } else {
45 result.push_back(static_cast<char>(byte));
46 }
47 }
48 }
49 result.push_back('"');
50 return result;
51}
52
53[[nodiscard]] int parseOccupant(const eve::container::ContainerObject& object) noexcept {
54 if (object.type != "vehicle.occupant" || object.quantity != 1u) return 0;
55 if (object.id.value().size() <= kOccupantPrefix.size() ||
56 object.id.value().compare(0, kOccupantPrefix.size(), kOccupantPrefix) != 0)
57 return 0;
58 const auto text = object.id.value().substr(kOccupantPrefix.size());
59 int value = 0;
60 const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value);
61 if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value <= 0) return 0;
62 return value;
63}
64
65[[nodiscard]] int payloadOccupant(const eve::container::ContainerObject& object) noexcept {
66 const auto* payload = dynamic_cast<const VehicleSeatContainerObject*>(object.payload.get());
67 if (payload == nullptr) return parseOccupant(object);
68 const auto expectedId =
69 eve::container::MembershipId(std::string(kOccupantPrefix) + std::to_string(payload->occupantId));
70 if (payload->occupantId <= 0 || object.id != expectedId) return 0;
71 return parseOccupant(object) == payload->occupantId ? payload->occupantId : 0;
72}
73
74[[nodiscard]] bool sameLayout(const eve::container::ContainerSnapshot& lhs,
76 if (lhs.id != rhs.id || lhs.revision != rhs.revision || lhs.entries.size() != rhs.entries.size()) return false;
77 for (const auto& left : lhs.entries) {
78 const auto right = std::find_if(rhs.entries.begin(), rhs.entries.end(), [&](const auto& entry) {
79 return entry.membership.object == left.membership.object;
80 });
81 if (right == rhs.entries.end() || left.membership.slot != right->membership.slot ||
82 left.membership.generation != right->membership.generation || left.object.id != right->object.id ||
83 left.object.type != right->object.type || left.object.quantity != right->object.quantity)
84 return false;
85 }
86 return true;
87}
88
89[[nodiscard]] game_event::GameEvent makeEvent(const eve::container::ContainerId& container,
90 const eve::container::MembershipId& object,
93 std::string_view reason = {}) {
95 if (!schema) std::terminate();
96
100 event.source = container.value();
101 event.subject = object.value();
102 event.schemaId = *schema;
103 event.schemaVersion = eve::SchemaVersion(1);
104 event.tick = tick;
105 event.payload = "{\"containerId\":" + quote(container.value()) + ",\"objectId\":" + quote(object.value()) +
106 ",\"slot\":" + std::to_string(slot.isValid() ? slot.value() : -1) + ",\"revision\":\"" +
107 std::to_string(revision.value()) + "\"";
108 if (!reason.empty()) event.payload += ",\"reason\":" + quote(reason);
109 event.payload += '}';
110 return event;
111}
112
113void notify(const eve::container::GameEventSink& sink, const eve::container::ContainerId& container,
116 std::string_view reason, bool& callbackFailed) noexcept {
117 if (!sink) return;
118 try {
119 const auto event = makeEvent(container, object, kind, slot, revision, serial, tick, reason);
120 sink(event);
121 } catch (...) {
122 callbackFailed = true;
123 }
124}
125
126[[nodiscard]] eve::Status appliedStatus(bool callbackFailed) {
127 if (!callbackFailed) return eve::Status::success(eve::StatusCode::Applied);
128 try {
129 std::vector<eve::Diagnostic> diagnostics;
130 diagnostics.emplace_back(eve::Diagnostic::warning(
131 eve::DiagnosticCode::CallbackFailure, "container membership committed but an event observer threw"));
132 return eve::Status(eve::StatusCode::Applied, std::move(diagnostics));
133 } catch (...) {
135 }
136}
137
138} // namespace
139
141public:
142 PreparedState(VehicleSeatContainerAdapter& owner, std::vector<std::optional<int>> staged, eve::Revision revision)
143 : owner_(owner), staged_(std::move(staged)), revision_(revision) {}
144
145 void commit() noexcept override {
146 if (committed_) return;
147 auto* vehicle = owner_.vehicle();
148 auto* seats = seatsOf(vehicle);
149 if (seats == nullptr || seats->list.size() != staged_.size()) std::terminate();
150 for (std::size_t index = 0; index < staged_.size(); ++index) {
151 auto& seat = seats->list[index];
152 seat.occupied = staged_[index].has_value();
153 seat.occupant = staged_[index].value_or(0);
154 }
155 seats->revision = revision_;
156 committed_ = true;
157 }
158
159 void rollback() noexcept override {
160 if (!committed_) staged_.clear();
161 }
162
163private:
165 std::vector<std::optional<int>> staged_;
166 eve::Revision revision_;
167 bool committed_ = false;
168};
169
172 : descriptor_{std::move(id),
173 vehicle != nullptr ? eve::container::Capacity::fixed(seatsOf(vehicle)->list.size())
174 : eve::container::Capacity::fixed(0),
175 eve::container::Ordering::ExplicitSlots, std::move(accepted)},
176 vehicle_(vehicle != nullptr ? ecs::handle_of(vehicle) : ecs::EntityHandle{}) {}
177
178VehicleEntity* VehicleSeatContainerAdapter::vehicle() const noexcept {
179 return static_cast<VehicleEntity*>(ecs::try_get(vehicle_));
180}
181
183 const auto* value = vehicle();
184 const auto* seats = seatsOf(value);
185 return seats == nullptr ? eve::Revision::zero() : seats->revision;
186}
187
188std::uint64_t VehicleSeatContainerAdapter::nextEventSerial() noexcept {
189 if (eventSerial_ != std::numeric_limits<std::uint64_t>::max()) ++eventSerial_;
190 return eventSerial_;
191}
192
193eve::container::MembershipId VehicleSeatContainerAdapter::occupantId(int occupantId) {
194 return eve::container::MembershipId(std::string(kOccupantPrefix) + std::to_string(occupantId));
195}
196
197eve::container::ContainerObject VehicleSeatContainerAdapter::describe(int occupantId, std::string driver) {
199 object.id = VehicleSeatContainerAdapter::occupantId(occupantId);
200 object.type = "vehicle.occupant";
201 object.quantity = 1;
202 auto payload = std::make_shared<VehicleSeatContainerObject>();
203 payload->occupantId = occupantId;
204 payload->driver = std::move(driver);
205 object.payload = std::move(payload);
206 return object;
207}
208
210 const auto* value = vehicle();
211 const auto* seats = seatsOf(value);
212 if (!descriptor_.id.isValid() || seats == nullptr)
214 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "vehicle seat adapter is stale or unbound"));
215 eve::container::ContainerSnapshot result{descriptor_.id, seats->revision, {}};
216 std::unordered_set<eve::container::MembershipId> identities;
217 result.entries.reserve(seats->list.size());
218 for (std::size_t index = 0; index < seats->list.size(); ++index) {
219 const auto& seat = seats->list[index];
220 if (!seat.occupied) continue;
221 if (seat.occupant <= 0 || index > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()))
223 eve::DiagnosticCode::InvariantViolation, "vehicle seat contains an invalid occupant"));
224 const auto id = occupantId(seat.occupant);
225 if (!identities.insert(id).second)
227 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "vehicle occupant appears in multiple seats"));
228 result.entries.push_back({{id, eve::container::SlotIndex(static_cast<std::int32_t>(index)), eve::Generation(1)},
229 describe(seat.occupant, seat.driver)});
230 }
232}
233
235 const eve::container::ContainerObject& object, std::optional<eve::container::SlotIndex> destination,
236 std::optional<eve::container::MembershipId> ignoredObject) const {
237 const auto* value = vehicle();
238 const auto* seats = seatsOf(value);
239 if (!descriptor_.id.isValid() || seats == nullptr)
241 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "vehicle seat adapter is stale or unbound"));
242 if (!object.id.isValid() || object.type != "vehicle.occupant" || object.quantity != 1u)
244 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "vehicle seat object facts are invalid"));
245 const int occupant = payloadOccupant(object);
246 if (occupant <= 0)
248 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "vehicle occupant identity is stale"));
249
250 const auto occupiedBy = [&](std::size_t index) {
251 const auto& seat = seats->list[index];
252 return seat.occupied && occupantId(seat.occupant) == object.id;
253 };
254 for (std::size_t index = 0; index < seats->list.size(); ++index) {
255 if (occupiedBy(index) && (!ignoredObject || *ignoredObject != object.id))
257 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "vehicle occupant is already seated"));
258 }
259 if (destination) {
260 if (!destination->isValid() || static_cast<std::size_t>(destination->value()) >= seats->list.size())
262 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "vehicle seat slot is out of range"));
263 const auto& seat = seats->list[static_cast<std::size_t>(destination->value())];
264 if (seat.occupied && (!ignoredObject || occupantId(seat.occupant) != *ignoredObject))
266 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "vehicle seat slot is occupied"));
267 }
268 auto accepted = descriptor_.filter.evaluate(object);
269 if (!accepted) return accepted;
270 std::size_t count = 0;
271 for (const auto& seat : seats->list)
272 if (seat.occupied && (!ignoredObject || occupantId(seat.occupant) != *ignoredObject)) ++count;
273 if (count >= seats->list.size())
275 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "vehicle has no free seat"));
277}
278
281 const auto* value = vehicle();
282 const auto* seats = seatsOf(value);
283 if (!descriptor_.id.isValid() || seats == nullptr)
285 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "vehicle seat adapter is stale or unbound"));
286 if (expected.id != descriptor_.id || candidate.id != descriptor_.id)
288 eve::DiagnosticCode::InvalidArgument, "vehicle seat prepare targets another container"));
289 auto current = snapshot();
290 if (!current)
292 if (!sameLayout(current.value(), expected))
294 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "vehicle seat expected snapshot is stale"));
295 const auto next = expected.revision.incremented();
296 if (!next || candidate.revision != *next)
298 eve::DiagnosticCode::InvariantViolation, "vehicle seat candidate revision is not the next revision"));
299 if (candidate.entries.size() > seats->list.size())
301 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "vehicle seat candidate exceeds capacity"));
302
303 std::vector<std::optional<int>> staged(seats->list.size());
304 std::unordered_set<eve::container::MembershipId> identities;
305 for (const auto& entry : candidate.entries) {
306 if (!entry.membership.object.isValid() || !entry.membership.slot.isValid() ||
307 static_cast<std::size_t>(entry.membership.slot.value()) >= staged.size() ||
308 entry.membership.generation.isZero() || entry.object.id != entry.membership.object ||
309 entry.object.type != "vehicle.occupant" || entry.object.quantity != 1u ||
310 !identities.insert(entry.membership.object).second)
311 return eve::Result<std::unique_ptr<eve::container::IContainer::PreparedState>>::failure(
313 "vehicle seat candidate contains invalid membership"));
314 const int occupant = payloadOccupant(entry.object);
315 if (occupant <= 0)
317 eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "vehicle seat candidate payload is stale"));
318 auto& slot = staged[static_cast<std::size_t>(entry.membership.slot.value())];
319 if (slot)
322 "vehicle seat candidate contains duplicate slots"));
323 slot = occupant;
324 }
325 std::unique_ptr<eve::container::IContainer::PreparedState> prepared =
326 std::make_unique<PreparedState>(*this, std::move(staged), *next);
328}
329
331 int occupant,
334 const auto serial = nextEventSerial();
335 const auto objectIdValue = occupantId(occupant);
336 auto current = snapshot();
337 if (!current) {
338 const auto status = current.status();
339 bool ignored = false;
340 const auto* diagnostic = status.primaryDiagnostic();
341 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Rejected, seatIndex, revision(),
342 serial, tick, diagnostic == nullptr ? "stale_container" : diagnostic->message(), ignored);
344 }
345 auto object = describe(occupant);
346 auto accepted = validateInsert(object, seatIndex, std::nullopt);
347 if (!accepted) {
348 const auto status = accepted.status();
349 bool ignored = false;
350 const auto* diagnostic = status.primaryDiagnostic();
351 notify(sink, descriptor_.id, object.id, eve::container::ContainerEventKind::Rejected, seatIndex,
352 current.value().revision, serial, tick, diagnostic == nullptr ? "rejected" : diagnostic->message(),
353 ignored);
355 }
356 auto candidate = current.value();
357 const auto next = candidate.revision.incremented();
358 if (!next) {
359 const auto status = eve::Status::failure(
360 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "vehicle seat revision exhausted"));
361 bool ignored = false;
362 notify(sink, descriptor_.id, object.id, eve::container::ContainerEventKind::Rejected, seatIndex,
363 candidate.revision, serial, tick, "revision_exhausted", ignored);
365 }
366 candidate.revision = *next;
367 candidate.entries.push_back({{object.id, seatIndex, eve::Generation(1)}, std::move(object)});
368 auto prepared = prepare(current.value(), candidate);
369 if (!prepared) {
370 const auto status = prepared.status();
371 bool ignored = false;
372 const auto* diagnostic = status.primaryDiagnostic();
373 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Rejected, seatIndex,
374 current.value().revision, serial, tick, diagnostic == nullptr ? "prepare_failed" : diagnostic->message(),
375 ignored);
377 }
378 auto staged = std::move(prepared).takeValue();
379 staged->commit();
380 bool callbackFailed = false;
381 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Accepted, seatIndex, *next, serial,
382 tick, {}, callbackFailed);
383 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Enter, seatIndex, *next, serial,
384 tick, {}, callbackFailed);
385 return eve::Result<eve::container::MembershipId>::success(objectIdValue, appliedStatus(callbackFailed));
386}
387
391 const auto serial = nextEventSerial();
392 auto current = snapshot();
393 eve::container::MembershipId objectIdValue;
394 if (current) {
395 for (const auto& entry : current.value().entries)
396 if (entry.membership.slot == seatIndex) objectIdValue = entry.membership.object;
397 }
398 if (!current || !objectIdValue.isValid()) {
400 "vehicle seat is already empty"))
401 : current.status();
402 bool ignored = false;
403 const auto* diagnostic = status.primaryDiagnostic();
404 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Rejected, seatIndex, revision(),
405 serial, tick, diagnostic == nullptr ? "rejected" : diagnostic->message(), ignored);
407 }
408 auto candidate = current.value();
409 candidate.entries.erase(std::remove_if(candidate.entries.begin(), candidate.entries.end(),
410 [&](const auto& entry) { return entry.membership.slot == seatIndex; }),
411 candidate.entries.end());
412 const auto next = candidate.revision.incremented();
413 if (!next) {
414 const auto status = eve::Status::failure(
415 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "vehicle seat revision exhausted"));
416 bool ignored = false;
417 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Rejected, seatIndex,
418 current.value().revision, serial, tick, "revision_exhausted", ignored);
420 }
421 candidate.revision = *next;
422 auto prepared = prepare(current.value(), candidate);
423 if (!prepared) {
424 const auto status = prepared.status();
425 bool ignored = false;
426 const auto* diagnostic = status.primaryDiagnostic();
427 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Rejected, seatIndex,
428 current.value().revision, serial, tick, diagnostic == nullptr ? "prepare_failed" : diagnostic->message(),
429 ignored);
431 }
432 auto staged = std::move(prepared).takeValue();
433 staged->commit();
434 bool callbackFailed = false;
435 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Accepted, seatIndex, *next, serial,
436 tick, {}, callbackFailed);
437 notify(sink, descriptor_.id, objectIdValue, eve::container::ContainerEventKind::Exit, seatIndex, *next, serial,
438 tick, {}, callbackFailed);
439 return eve::Result<eve::container::MembershipId>::success(objectIdValue, appliedStatus(callbackFailed));
440}
441
442} // namespace eve::vehicle
double value
Value::Object payload
wgpu::PopErrorScopeStatus status
HexVec3 left
HexVec3 right
std::string text
TokenKind kind
char quote
std::uint64_t revision
std::string id
Definition PlayHost.cpp:108
double current
std::uint32_t count
SimulationTick tick
Json object
float size
Definition TreeMesh.cpp:156
uint64_t serial
Definition UISystem.cpp:68
uint32_t index
static Diagnostic warning(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct a warning diagnostic.
Definition Diagnostic.h:134
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Strong identity of one runtime or persistent container.
Definition Container.h:44
bool isValid() const noexcept
Return whether this ID is usable by a transfer request.
Definition Container.h:58
const std::string & value() const noexcept
Return the owned stable spelling.
Definition Container.h:60
Extensible filter result used by container and zone acceptance rules.
Definition Container.h:139
Result< void > evaluate(const struct ContainerObject &object) const
Evaluate the filter; an empty filter accepts every object.
Definition Container.h:149
Detached state prepared for an atomic container replacement.
Definition Container.h:296
Strong identity of an object which can have one container membership.
Definition Container.h:68
bool isValid() const noexcept
Return whether this identity is usable.
Definition Container.h:82
Explicit slot number; it never implicitly converts to an integer.
Definition Container.h:117
constexpr std::int32_t value() const noexcept
Value.
Definition Container.h:128
constexpr bool isValid() const noexcept
True when valid.
Definition Container.h:126
constexpr std::optional< StrongUint64 > incremented() const noexcept
Returns the next value, or empty instead of unsigned wraparound.
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
载具实体:数据全部在组件里,行为在 VehicleSystem。
void commit() noexcept override
Publish the staged state; the operation is non-throwing.
void rollback() noexcept override
Abandon an unpublished stage; the operation is non-throwing.
PreparedState(VehicleSeatContainerAdapter &owner, std::vector< std::optional< int > > staged, eve::Revision revision)
Transactional adapter over one VehicleEntity::Seats component.
eve::Result< std::unique_ptr< eve::container::IContainer::PreparedState > > prepare(const eve::container::ContainerSnapshot &expected, const eve::container::ContainerSnapshot &candidate) override
Prepare an exact replacement from expected to candidate.
eve::Result< void > validateInsert(const eve::container::ContainerObject &object, std::optional< eve::container::SlotIndex > destination, std::optional< eve::container::MembershipId > ignoredObject=std::nullopt) const override
Validate insert.
eve::Result< eve::container::ContainerSnapshot > snapshot() const override
Snapshot.
eve::Result< eve::container::MembershipId > exit(eve::container::SlotIndex seatIndex, eve::container::GameEventSink sink={}, eve::SimulationTick tick=eve::SimulationTick::zero())
Stage and publish the occupant leaving a seat.
eve::Result< eve::container::MembershipId > enter(eve::container::SlotIndex seatIndex, int occupantId, eve::container::GameEventSink sink={}, eve::SimulationTick tick=eve::SimulationTick::zero())
Stage and publish an occupant entering a seat.
eve::Revision revision() const noexcept
Return the live seat-component revision, or zero when stale.
VehicleSeatContainerAdapter(eve::container::ContainerId id, VehicleEntity *vehicle, eve::container::AcceptedCondition accepted={})
Binds an adapter to a live ECS vehicle.
constexpr std::string_view containerEventSchemaName
Canonical schema name for container lifecycle envelopes.
Definition Container.h:232
ContainerEventKind
Lifecycle outcome represented by every container-domain event.
Definition Container.h:205
const char * containerEventTypeName(ContainerEventKind kind) noexcept
Stable event type spelling shared by all container adapters.
Definition Container.h:221
std::function< void(const game_event::GameEvent &)> GameEventSink
Callback receiving a canonical game_event envelope.
Definition Container.h:245
EventId deterministicContainerEventId(const ContainerId &container, const MembershipId &object, ContainerEventKind kind, std::uint64_t serial) noexcept
Derives a deterministic event identity for an adapter lifecycle event.
Definition Container.h:257
驾驶者接口与玩家控制状态。
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
detail::StrongUint64< detail::GenerationTag > Generation
Registry/object replacement generation used to reject stale handles.
Definition Generation.h:13
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Generic object facts used by filters and transfer diagnostics.
Definition Container.h:168
Lossless snapshot of one container at one revision.
Definition Container.h:190
std::vector< MembershipEntry > entries
Definition Container.h:193
Envelope metadata shared by event domains.
Definition GameEvent.h:183
EventId eventId
Stable event identity; nil is allowed only for legacy transient events.
Definition GameEvent.h:185
std::string payload
Canonical serialized payload at the stream persistence boundary.
Definition GameEvent.h:207
座位列表(FPS 面)。
Owning snapshot payload for a vehicle occupant membership.
Vehicle adapters for the common Container/Zone/Transfer protocol.