| 351 | } |
| 352 | |
| 353 | void DataEngine::removeDataset(DatasetId dataset_id) { |
| 354 | // Hold the engine lock for the whole erase, like every other mutator — a worker may |
| 355 | // be ingesting a different dataset concurrently (GUI-thread caller; recursive_mutex). |
| 356 | auto lock = lockEngine(); |
| 357 | auto ds_it = impl_->datasets.find(dataset_id); |
| 358 | if (ds_it == impl_->datasets.end()) { |
| 359 | return; // unknown id — nothing to remove |
| 360 | } |
| 361 | // REAL delete (vs retireTopic's hide-but-keep): free every topic's TopicStorage. |
| 362 | // PRECONDITION: the caller invalidated all readers/adapters bound to this dataset |
| 363 | // first (same contract as replaceDatasetFrom) — erasing frees the memory a cached |
| 364 | // reader / TopicChunk* would otherwise dereference. |
| 365 | for (const TopicId tid : ds_it->second.topic_ids) { |
| 366 | impl_->topics.erase(tid); |
| 367 | impl_->retired_topic_ids.erase(tid); // drop retire bookkeeping for the now-gone topic |
| 368 | } |
| 369 | impl_->datasets.erase(ds_it); |
| 370 | // next_*_id are NOT rewound: strictly-incrementing ids guarantee a later dataset / |
| 371 | // topic never aliases an erased one. time_domains are left intact (shared + cheap). |
| 372 | } |
| 373 | |
| 374 | void DataEngine::clearDatasetChunks(DatasetId dataset_id) { |
| 375 | // Like retireTopic, reclaim each topic's materialized chunks — but WITHOUT |