stage/publish/publisher.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/stage/publish/publisher.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <optional> | ||
| 6 | |||
| 7 | #include "chrono_batch_render/device/commands.h" | ||
| 8 | #include "chrono_batch_render/device/queue.h" | ||
| 9 | #include "chrono_batch_render/stage/columns/kernels/changes.h" | ||
| 10 | #include "chrono_batch_render/util/error.h" | ||
| 11 | #include "chrono_batch_render/util/log.h" | ||
| 12 | #include "chrono_batch_render/util/profiler.h" | ||
| 13 | |||
| 14 | namespace chrono::batch_render { | ||
| 15 | |||
| 16 | 683 | void Publisher::Finish() { | |
| 17 | 12977 | for (const Scheduled& scheduled : m_scheduled) { | |
| 18 | 12294 | scheduled.table->Finish(); | |
| 19 | } | ||
| 20 | 683 | } | |
| 21 | |||
| 22 | 21841 | bool Publisher::Pending() const { | |
| 23 | 21841 | return m_structure.GetColumnStore().WriteSequence() != m_writes || | |
| 24 | 42368 | m_structure.Epoch() != m_epoch || | |
| 25 | 20527 | std::ranges::any_of( | |
| 26 | 167689 | m_scheduled, [](const Scheduled& scheduled) { return scheduled.table->Pending(); }); | |
| 27 | } | ||
| 28 | |||
| 29 | 6526 | void Publisher::Add(Table& table) { | |
| 30 | 11716 | for (const TableId output : table.TableOutputs()) { | |
| 31 | 11976 | for (const TableId input : table.TableInputs()) { | |
| 32 | 6785 | if (input == output) { | |
| 33 | 1 | throw Error("Table {} reads its own output", table.Name()); | |
| 34 | } | ||
| 35 | } | ||
| 36 | 28299 | for (const Scheduled& earlier : m_scheduled) { | |
| 37 | 27345 | for (const TableId input : earlier.table->TableInputs()) { | |
| 38 | 4237 | if (input == output) { | |
| 39 | 1 | throw Error("Table {} writes a table {} reads, so add {} first", table.Name(), | |
| 40 | 2 | earlier.table->Name(), table.Name()); | |
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | 9410 | for (const ColumnRef column : table.RowInputs()) { | |
| 46 | 2886 | m_structure.GetColumnStore().TrackRows(column); | |
| 47 | } | ||
| 48 | 6524 | m_scheduled.push_back({&table}); | |
| 49 | 6524 | } | |
| 50 | |||
| 51 | 9 | Ordinal Publisher::SealedStamp(Ordinal ordinal) const { | |
| 52 | 9 | const auto after = std::ranges::upper_bound(m_sealed, ordinal, {}, &Sealed::ordinal); | |
| 53 | 16 | return after == m_sealed.begin() ? 0 : std::prev(after)->stamp; | |
| 54 | } | ||
| 55 | |||
| 56 | 148163 | Ordinal Publisher::SealedOrdinal(Ordinal stamp, Ordinal sealing) const { | |
| 57 | 148163 | const auto found = std::ranges::lower_bound(m_sealed, stamp, {}, &Sealed::stamp); | |
| 58 | 148163 | return found == m_sealed.end() ? sealing : found->ordinal; | |
| 59 | } | ||
| 60 | |||
| 61 | 10474 | ChangeSet Publisher::Build(Ordinal since, Ordinal until, Ordinal sealing, | |
| 62 | std::span<const ColumnRef> columns, std::span<const ColumnRef> rows, | ||
| 63 | std::vector<Ref<device::Buffer>>* reused) { | ||
| 64 | 10474 | CHRONO_BATCH_RENDER_ZONE("Publisher/Changes"); | |
| 65 | 10474 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 66 | 10474 | ChangeSet changes; | |
| 67 | 10474 | changes.since = since == 0 ? 0 : SealedOrdinal(since, sealing); | |
| 68 | 10474 | changes.until = SealedOrdinal(until, sealing); | |
| 69 | 10474 | changes.sinceStamp = since; | |
| 70 | 10474 | changes.untilStamp = until; | |
| 71 | 10474 | changes.resync = since == 0; | |
| 72 | 10474 | { | |
| 73 | 10474 | CHRONO_BATCH_RENDER_ZONE("Publisher/StructureLog"); | |
| 74 | 10474 | const std::span<const StructureEntry> log = m_structure.Log(); | |
| 75 | 142804 | for (auto at = std::ranges::upper_bound(log, since, {}, &StructureEntry::ordinal); | |
| 76 | 142804 | at != log.end() && at->ordinal <= until; ++at) { | |
| 77 | 132330 | StructureEntry entry = *at; | |
| 78 | 132330 | entry.ordinal = SealedOrdinal(entry.ordinal, sealing); | |
| 79 | 132330 | if (entry.api != SchemaId::None) { | |
| 80 | 379 | changes.apis.push_back(entry); | |
| 81 | } else { | ||
| 82 | 131951 | (entry.added ? changes.added : changes.removed).push_back(entry); | |
| 83 | } | ||
| 84 | } | ||
| 85 | 10474 | changes.structure = !changes.added.empty() || !changes.removed.empty(); | |
| 86 | 10474 | } | |
| 87 | 10474 | { | |
| 88 | 10474 | CHRONO_BATCH_RENDER_ZONE("Publisher/Worlds"); | |
| 89 | 10474 | const uint32_t worlds = m_structure.GetPrims().WorldCount(); | |
| 90 | 22480 | for (uint32_t slot = 0; slot <= worlds; ++slot) { | |
| 91 | 12006 | const uint32_t world = SlotWorld(slot); | |
| 92 | 12006 | const Ordinal stamp = store.WorldStamp(world); | |
| 93 | 12006 | if (stamp > since && stamp <= until) { | |
| 94 | 11386 | changes.worlds.push_back(world); | |
| 95 | } | ||
| 96 | } | ||
| 97 | 10474 | std::ranges::sort(changes.worlds); | |
| 98 | 10474 | } | |
| 99 | 10474 | { | |
| 100 | 10474 | CHRONO_BATCH_RENDER_ZONE("Publisher/ArrayRows"); | |
| 101 | 278813 | for (const ColumnRef column : columns) { | |
| 102 | 268339 | if (IsArray(column.column->type)) { | |
| 103 | 61316 | changes.arrays.emplace_back(column, store.ArrayRows(column, since, until)); | |
| 104 | } | ||
| 105 | } | ||
| 106 | 10474 | } | |
| 107 | 10474 | if (rows.empty()) { | |
| 108 | return changes; | ||
| 109 | } | ||
| 110 | 564 | CHRONO_BATCH_RENDER_ZONE("Publisher/CompactRows"); | |
| 111 | 564 | if (reused != nullptr) { | |
| 112 | 558 | reused->resize(rows.size()); | |
| 113 | } | ||
| 114 | 564 | device::Queue& queue = store.GetDevice().GetQueue(); | |
| 115 | 564 | std::optional<device::CommandEncoder> encoder; | |
| 116 | 4635 | for (size_t i = 0; i < rows.size(); ++i) { | |
| 117 | 4071 | const ColumnRef column = rows[i]; | |
| 118 | 4071 | store.TrackRows(column); | |
| 119 | 4071 | const ColumnTable& table = store.Schema(column.schema); | |
| 120 | 4071 | const uint32_t count = table.Rows(); | |
| 121 | 4071 | const uint64_t bytes = | |
| 122 | 4071 | (uint64_t(kChangedRowsOffset) + std::max(count, 1u)) * sizeof(uint32_t); | |
| 123 | 4071 | Ref<device::Buffer> buffer = reused != nullptr ? (*reused)[i] : nullptr; | |
| 124 | 4071 | if (!buffer || buffer->Bytes() < bytes) { | |
| 125 | 2300 | buffer = store.GetDevice().CreateBuffer({.bytes = bytes, .label = "changed rows"}); | |
| 126 | 2280 | if (reused != nullptr) { | |
| 127 | 2294 | (*reused)[i] = buffer; | |
| 128 | } | ||
| 129 | } | ||
| 130 | 4071 | if (!encoder) { | |
| 131 | 1128 | encoder.emplace(queue.CreateEncoder()); | |
| 132 | } | ||
| 133 | 4071 | encoder->Fill(buffer->Slice(0, kChangedRowsOffset * sizeof(uint32_t))); | |
| 134 | 4071 | encoder->Barrier(); | |
| 135 | 4071 | CompactParams params{}; | |
| 136 | 4071 | params.stamps.address = table.Pages(store.StampColumn(column)); | |
| 137 | 4071 | params.tablePrims.address = table.Pages(store.PrimsColumn(column.schema)); | |
| 138 | 4071 | params.out.address = buffer->Address(); | |
| 139 | 4071 | params.since = since; | |
| 140 | 4071 | params.until = until; | |
| 141 | 4071 | params.rows = count; | |
| 142 | 4071 | store.Record(*encoder, "CompactChanged", params, count); | |
| 143 | 4071 | changes.columns.emplace_back(column, buffer, count); | |
| 144 | 4071 | } | |
| 145 | 564 | if (encoder) { | |
| 146 | 564 | queue.Submit(encoder->Finish()); | |
| 147 | } | ||
| 148 | 564 | return changes; | |
| 149 | 15034 | } | |
| 150 | |||
| 151 | 1203 | bool Publisher::RunTables(Ordinal stamp, Ordinal ordinal, std::span<Table* const> only) { | |
| 152 | 1203 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 153 | 1203 | bool ran = false; | |
| 154 | 18939 | for (Scheduled& scheduled : m_scheduled) { | |
| 155 | 17737 | Table& table = *scheduled.table; | |
| 156 | 17737 | if (!only.empty() && std::ranges::find(only, &table) == only.end()) { | |
| 157 | 7272 | continue; | |
| 158 | } | ||
| 159 | 17125 | const bool written = std::ranges::any_of(table.Inputs(), [&](ColumnRef input) { | |
| 160 | 308976 | return store.LastWrite(input) > scheduled.writes; | |
| 161 | }); | ||
| 162 | 17125 | const bool restructured = table.Structural() && m_structure.Epoch() != scheduled.epoch; | |
| 163 | 17125 | const bool tabled = std::ranges::any_of(table.TableInputs(), [&](TableId id) { | |
| 164 | 13286 | return m_versions.Version(id) > scheduled.logged; | |
| 165 | }); | ||
| 166 | 17125 | if (scheduled.ran && !written && !restructured && !tabled && !table.Pending()) { | |
| 167 | 6660 | continue; | |
| 168 | } | ||
| 169 | 10465 | CHRONO_BATCH_RENDER_ZONE("Publisher/Table"); | |
| 170 | 20930 | ChangeSet changes = Build(scheduled.ran ? scheduled.stamp : 0, stamp, ordinal, | |
| 171 | 10465 | table.Inputs(), table.RowInputs(), &scheduled.changed); | |
| 172 | 10465 | { | |
| 173 | 10465 | CHRONO_BATCH_RENDER_ZONE("Publisher/Versions"); | |
| 174 | 30186 | for (const TableId id : table.TableInputs()) { | |
| 175 | 39331 | if (m_versions.Version(id) > scheduled.logged) { | |
| 176 | 8760 | changes.wrote.push_back(id); | |
| 177 | 8760 | m_versions.Collect(id, scheduled.logged, changes.worlds); | |
| 178 | } | ||
| 179 | } | ||
| 180 | 10465 | } | |
| 181 | 10465 | const uint64_t writes = store.WriteSequence(); | |
| 182 | 10465 | const uint64_t epoch = m_structure.Epoch(); | |
| 183 | 10465 | const uint64_t logged = m_versions.Sequence(); | |
| 184 | 10465 | ++table.m_runs; | |
| 185 | 10465 | ++m_tableRuns; | |
| 186 | 10465 | table.Run(changes, m_versions); | |
| 187 | 10464 | if (m_header != nullptr) { | |
| 188 | 10464 | m_header->Describe(table); | |
| 189 | } | ||
| 190 | 10464 | scheduled.writes = writes; | |
| 191 | 10464 | scheduled.epoch = epoch; | |
| 192 | 10464 | scheduled.logged = logged; | |
| 193 | 10464 | scheduled.stamp = stamp; | |
| 194 | 10464 | scheduled.ran = true; | |
| 195 | 10464 | ran = true; | |
| 196 | 10466 | } | |
| 197 | 1202 | return ran; | |
| 198 | } | ||
| 199 | |||
| 200 | 1166 | void Publisher::Trim(Ordinal stamp) { | |
| 201 | 1166 | CHRONO_BATCH_RENDER_ZONE("Publisher/Trim"); | |
| 202 | 1166 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 203 | 1166 | Ordinal seen = stamp; | |
| 204 | 1166 | std::vector<std::pair<ColumnRef, Ordinal>> held; | |
| 205 | 18253 | for (const Scheduled& scheduled : m_scheduled) { | |
| 206 | 17087 | if (scheduled.table->Structural()) { | |
| 207 | 16629 | seen = std::min(seen, scheduled.stamp); | |
| 208 | } | ||
| 209 | 439947 | for (const ColumnRef column : scheduled.table->Inputs()) { | |
| 210 | 422860 | if (!IsArray(column.column->type)) { | |
| 211 | 329426 | continue; | |
| 212 | } | ||
| 213 | 93434 | const auto found = std::ranges::find_if( | |
| 214 | 2663905 | held, [&](const auto& entry) { return entry.first.column == column.column; }); | |
| 215 | 93434 | if (found == held.end()) { | |
| 216 | 55256 | held.emplace_back(column, scheduled.stamp); | |
| 217 | } else { | ||
| 218 | 40148 | found->second = std::min(found->second, scheduled.stamp); | |
| 219 | } | ||
| 220 | } | ||
| 221 | } | ||
| 222 | 1166 | store.TrimArrayWrites(stamp, held); | |
| 223 | 1166 | if (!m_scheduled.empty()) { | |
| 224 | 1153 | m_structure.TrimLog(seen); | |
| 225 | } | ||
| 226 | 1166 | uint64_t logged = m_versions.Sequence(); | |
| 227 | 18253 | for (const Scheduled& scheduled : m_scheduled) { | |
| 228 | 17087 | if (!scheduled.table->TableInputs().empty()) { | |
| 229 | 7637 | logged = std::min(logged, scheduled.logged); | |
| 230 | } | ||
| 231 | } | ||
| 232 | 1166 | m_versions.Trim(logged); | |
| 233 | 1166 | } | |
| 234 | |||
| 235 | 16861 | device::Operation Publisher::Publish(Ordinal ordinal, Stream producer) { | |
| 236 | 16861 | CheckStoreLock(m_structure.GetColumnStore().Lock(), "Publishing a scene"); | |
| 237 | 16861 | CHRONO_BATCH_RENDER_ZONE("Publisher/Publish"); | |
| 238 | 16861 | if (!m_sealed.empty() && ordinal <= m_sealed.back().ordinal) { | |
| 239 | 2 | throw Error("Publishing ordinal {}, but {} is already published", ordinal, | |
| 240 | 2 | m_sealed.back().ordinal); | |
| 241 | } | ||
| 242 | 16859 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 243 | 16859 | device::Queue& queue = store.GetDevice().GetQueue(); | |
| 244 | 16859 | queue.Follow(producer); | |
| 245 | 16859 | const Ordinal stamp = store.Stamp(); | |
| 246 | 16859 | device::Operation done; | |
| 247 | 16859 | if (Pending()) { | |
| 248 | 1167 | queue.BeginBatch(); | |
| 249 | 1167 | try { | |
| 250 | 1167 | store.Flush(); | |
| 251 | 1167 | RunTables(stamp, ordinal); | |
| 252 | 1166 | Trim(stamp); | |
| 253 | 1166 | Seal(); | |
| 254 | 1166 | store.DeviceData(); | |
| 255 | 1 | } catch (...) { | |
| 256 | 1 | queue.EndBatch(); | |
| 257 | 1 | store.SetStamp(stamp + 1); | |
| 258 | 1 | throw; | |
| 259 | 1 | } | |
| 260 | 1166 | done = queue.EndBatch(); | |
| 261 | } | ||
| 262 | 16858 | m_sealed.push_back({ordinal, stamp}); | |
| 263 | 16858 | if (m_sealed.size() > 2 * kSealedHistory) { | |
| 264 | 1 | m_sealed.erase(m_sealed.begin(), m_sealed.end() - ptrdiff_t(kSealedHistory)); | |
| 265 | } | ||
| 266 | 16858 | store.SetStamp(stamp + 1); | |
| 267 | 16858 | m_writes = store.WriteSequence(); | |
| 268 | 16858 | m_epoch = m_structure.Epoch(); | |
| 269 | 16858 | return done; | |
| 270 | 16861 | } | |
| 271 | |||
| 272 | 36 | device::Operation Publisher::Refresh(std::span<Table* const> tables) { | |
| 273 | 36 | CheckStoreLock(m_structure.GetColumnStore().Lock(), "Refreshing a scene's tables"); | |
| 274 | 36 | CHRONO_BATCH_RENDER_ZONE("Publisher/Refresh"); | |
| 275 | 36 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 276 | 36 | device::Queue& queue = store.GetDevice().GetQueue(); | |
| 277 | 36 | const Ordinal stamp = store.Stamp(); | |
| 278 | 36 | queue.BeginBatch(); | |
| 279 | 36 | bool ran = false; | |
| 280 | 36 | try { | |
| 281 | 36 | store.Flush(); | |
| 282 | 36 | ran = RunTables(stamp, Published() + 1, tables); | |
| 283 | 36 | store.DeviceData(); | |
| 284 | ✗ | } catch (...) { | |
| 285 | ✗ | queue.EndBatch(); | |
| 286 | ✗ | store.SetStamp(stamp + 1); | |
| 287 | ✗ | throw; | |
| 288 | ✗ | } | |
| 289 | 36 | const device::Operation done = queue.EndBatch(); | |
| 290 | 36 | if (ran) { | |
| 291 | 33 | store.SetStamp(stamp + 1); | |
| 292 | } | ||
| 293 | 36 | return done; | |
| 294 | 36 | } | |
| 295 | |||
| 296 | 10 | ChangeSet Publisher::Changes(Ordinal since, std::span<const ColumnRef> columns) { | |
| 297 | 10 | if (since > Published()) { | |
| 298 | 1 | throw Error("Changes since {}, but only {} is published", since, Published()); | |
| 299 | } | ||
| 300 | 9 | const Ordinal until = m_sealed.empty() ? 0 : m_sealed.back().stamp; | |
| 301 | 9 | return Build(SealedStamp(since), until, Published(), columns, columns); | |
| 302 | } | ||
| 303 | |||
| 304 | 1167 | void Publisher::Seal() { | |
| 305 | 1167 | CHRONO_BATCH_RENDER_ZONE("Publisher/Seal"); | |
| 306 | 1167 | auto pinned = std::make_shared<PinnedTables>(); | |
| 307 | 1167 | if (m_header != nullptr) { | |
| 308 | 1167 | const std::span<const std::byte> header = m_header->Seal( | |
| 309 | 1167 | m_structure.GetColumnStore().Data(), m_structure.GetPrims().WorldCount()); | |
| 310 | 1167 | pinned->header.assign(header.begin(), header.end()); | |
| 311 | } | ||
| 312 | 1167 | if (m_pinned && m_pinned->header == pinned->header) { | |
| 313 | 493 | pinned->buffer = m_pinned->buffer; | |
| 314 | 674 | } else if (!pinned->header.empty()) { | |
| 315 | 1348 | pinned->buffer = m_structure.GetColumnStore().GetDevice().CreateBuffer( | |
| 316 | 1348 | {.label = "snapshot header"}, std::span<const std::byte>(pinned->header)); | |
| 317 | } | ||
| 318 | 1167 | if (pinned->buffer) { | |
| 319 | 1167 | pinned->held.emplace_back(pinned->buffer); | |
| 320 | } | ||
| 321 | 1167 | pinned->content = m_tableRuns; | |
| 322 | 18272 | for (const Scheduled& scheduled : m_scheduled) { | |
| 323 | 17105 | scheduled.table->Hold(pinned->held); | |
| 324 | 17105 | pinned->versions.emplace_back(scheduled.table->Name(), scheduled.table->Runs()); | |
| 325 | } | ||
| 326 | 1167 | m_pinned = std::move(pinned); | |
| 327 | 2515 | } | |
| 328 | |||
| 329 | 3868 | Snapshot Publisher::Acquire(Stream consumer) { | |
| 330 | 3868 | if (m_sealed.empty()) { | |
| 331 | 4 | throw Error("Nothing is published yet"); | |
| 332 | } | ||
| 333 | 3866 | ColumnStore& store = m_structure.GetColumnStore(); | |
| 334 | 3866 | Snapshot snapshot; | |
| 335 | 3866 | snapshot.ordinal = Published(); | |
| 336 | 3866 | store.DeviceData(); | |
| 337 | 3866 | if (!m_pinned) { | |
| 338 | 1 | Seal(); | |
| 339 | } | ||
| 340 | 3866 | snapshot.tables = m_pinned->buffer ? m_pinned->buffer->Address() : 0; | |
| 341 | 3866 | snapshot.pinned = m_pinned; | |
| 342 | 3866 | snapshot.ready = store.GetDevice().GetQueue().Mark(); | |
| 343 | 3866 | snapshot.ready.Wait(consumer); | |
| 344 | 3866 | ++m_outstanding; | |
| 345 | 3866 | return snapshot; | |
| 346 | ✗ | } | |
| 347 | |||
| 348 | 3867 | void Publisher::Release(const Snapshot& snapshot, Stream consumer) { | |
| 349 | 3867 | if (m_outstanding == 0 || snapshot.ordinal == 0) { | |
| 350 | 2 | throw Error("Releasing a snapshot that wasn't acquired"); | |
| 351 | } | ||
| 352 | 3866 | m_structure.GetColumnStore().GetDevice().GetQueue().Follow(consumer); | |
| 353 | 3866 | --m_outstanding; | |
| 354 | 3866 | } | |
| 355 | |||
| 356 | 4 | uint64_t Snapshot::Version() const { | |
| 357 | 4 | return pinned ? PinnedTables::From(*this).content : 0; | |
| 358 | } | ||
| 359 | |||
| 360 | 343 | uint64_t Snapshot::Version(std::string_view table) const { | |
| 361 | 343 | if (!pinned) { | |
| 362 | return 0; | ||
| 363 | } | ||
| 364 | 343 | const PinnedTables& held = PinnedTables::From(*this); | |
| 365 | 3309 | for (const auto& [name, version] : held.versions) { | |
| 366 | 3308 | if (name == table) { | |
| 367 | 342 | return version; | |
| 368 | } | ||
| 369 | } | ||
| 370 | 1 | std::string names; | |
| 371 | 19 | for (const auto& [name, version] : held.versions) { | |
| 372 | 54 | names += (names.empty() ? "" : ", ") + std::string(name); | |
| 373 | } | ||
| 374 | 1 | throw Error("A snapshot has no table \"{}\", only {}", table, names); | |
| 375 | 1 | } | |
| 376 | |||
| 377 | } | ||
| 378 |