41 result.insert( offset, wxString::Format(
"_%u", aSuffix ) );
57 std::vector<NODE_ID> oldNodes;
58 std::vector<NODE_ID> newNodes;
61 using PREDECESSORS = std::map<NODE_ID, PREDECESSOR>;
65 const SESSION_KEYS& keys;
69 return keys.Less( keys.Node( aLeft ), keys.Node( aRight ) );
75 const auto nodes = std::views::keys( aComponents );
76 std::vector<NODE_ID>
result( nodes.begin(), nodes.end() );
77 std::ranges::sort(
result, NODE_LESS{ aKeys } );
83 std::vector<RECORD_KEY>
result;
85 for(
const auto& [node, version] : aPartition.identity )
87 if(
const auto* record = std::get_if<RECORD_NODE>( &aKeys.Node( node ) ) )
88 result.push_back( record->record );
96 const std::set<NODE_ID>& aUnchanged, PREDECESSORS& aPredecessors,
99 std::vector<SUCCESSION>
result;
103 std::map<ITEM_KEY, NODE_ID, KEY_LESS> items(
KEY_LESS{ aKeys } );
104 std::map<SLOT_KEY, NODE_ID, KEY_LESS> slots(
KEY_LESS{ aKeys } );
105 std::map<NODE_ID, std::vector<NODE_ID>> oldEdges;
106 std::map<NODE_ID, std::vector<NODE_ID>> newEdges;
108 for(
const auto& [node, component] : aOld )
110 if( component.content->kind != kind || aUnchanged.contains( node ) )
115 for(
const ITEM_KEY& item : component.content->items )
116 items.emplace( item, node );
118 for(
const SLOT_KEY& slot : component.content->slots )
119 slots.emplace( slot, node );
122 for(
const auto& [node, component] : aNew )
124 if( component.content->kind != kind )
127 if( aUnchanged.contains( node ) )
129 aPredecessors.emplace( node, PREDECESSOR{ node, component.content->items.size()
130 + component.content->slots.size() } );
134 auto& edges = newEdges[node];
135 std::map<NODE_ID, size_t> weights;
137 for(
const ITEM_KEY& item : component.content->items )
139 if(
const auto found = items.find( item ); found != items.end() )
140 ++weights[found->second];
143 for(
const SLOT_KEY& slot : component.content->slots )
145 if(
const auto found = slots.find( slot ); found != slots.end() )
146 ++weights[found->second];
149 for(
const auto& [old, weight] : weights )
151 edges.push_back( old );
152 oldEdges[old].push_back( node );
153 auto [found, inserted] = aPredecessors.emplace( node, PREDECESSOR{ old, weight } );
156 && ( weight > found->second.weight
157 || ( weight == found->second.weight && NODE_LESS{ aKeys }( old, found->second.node ) ) ) )
158 found->second = { old, weight };
162 std::set<NODE_ID> oldVisited;
163 std::set<NODE_ID> newVisited;
164 const auto visit = [&](
bool aOldSide,
NODE_ID aNode )
167 std::vector<std::pair<bool, NODE_ID>> pending{ { aOldSide, aNode } };
169 while( !pending.empty() )
171 const auto [oldSide, node] = pending.back();
173 auto& visited = oldSide ? oldVisited : newVisited;
175 if( !visited.insert( node ).second )
178 ( oldSide ?
group.oldNodes :
group.newNodes ).push_back( node );
180 for(
NODE_ID adjacent : ( oldSide ? oldEdges : newEdges ).at( node ) )
181 pending.emplace_back( !oldSide, adjacent );
187 for(
const auto& [node, edges] : oldEdges )
189 if( !oldVisited.contains( node ) )
193 for(
const auto& [node, edges] : newEdges )
195 if( !newVisited.contains( node ) )
196 visit(
false, node );
203 wxString Suffix( uint32_t aSuffix )
205 return aSuffix ? wxString::Format(
"_%u", aSuffix ) : wxString();
210 const wxString& full = aKeys.Name( aClaim.fullName );
211 return full.Left( full.length() - aKeys.Name( aClaim.name ).length() );
218 && !aComponent.content->best->schema->prefix.IsEmpty() )
220 const CLAIM& claim = *aComponent.content->best;
221 return BundleScope( claim, aKeys )
222 +
ApplyNameSuffix( aKeys.Name( claim.name ), claim.schema.get(), aSuffix );
225 return aKeys.Name( aComponent.baseName ) + Suffix( aSuffix );
229 const PREDECESSORS& aPredecessors,
const std::vector<NODE_ID>& aOrder,
SESSION_KEYS& aKeys )
231 std::map<NAME_ID, std::vector<NODE_ID>> buckets;
232 std::set<NAME_ID> occupied;
236 const auto& component = aCurrent.at( node );
238 if( component.content->kind == aKind && component.content->best )
239 buckets[component.collisionBase].push_back( node );
241 occupied.insert( component.name );
246 const auto found = aPredecessors.find( node );
247 return found == aPredecessors.end() ? nullptr : &aOld.at( found->second.node );
249 const auto rendered = [&](
NODE_ID node, uint32_t suffix )
251 const auto& component = aCurrent.at( node );
252 const auto* old = previous( node );
254 if( old && old->baseName == component.baseName && old->collisionBase == component.collisionBase
255 && old->suffix == suffix )
258 || ( old->content->best->name == component.content->best->name
259 && old->content->best->schema->shape == component.content->best->schema->shape
260 && old->content->best->schema->prefix == component.content->best->schema->prefix ) )
264 return aKeys.InternName( RenderName( component, suffix, aKeys ) );
267 const NODE_LESS nodeLess{ aKeys };
268 std::vector<NODE_ID> remaining;
270 for(
const auto& [base, members] : buckets )
274 const CLAIM&
left = *aCurrent.at( a ).content->best;
275 const CLAIM&
right = *aCurrent.at( b ).content->best;
290 const auto weight = [&](
NODE_ID node ) ->
size_t
292 const auto* old = previous( node );
293 return old && old->collisionBase == base && old->suffix == 0 ? aPredecessors.at( node ).weight
297 if( weight( a ) != weight( b ) )
298 return weight( a ) > weight( b );
301 return nodeLess( a, b );
304 const NODE_ID holder = *std::ranges::min_element( members, preferred );
305 auto& component = aCurrent.at( holder );
308 if( !occupied.insert(
name ).second )
309 throw std::invalid_argument(
"Distinct collision bases render name: "
310 + std::string( aKeys.Name(
name ).utf8_str() ) );
312 component.name =
name;
313 std::ranges::remove_copy( members, std::back_inserter( remaining ), holder );
316 std::ranges::sort( remaining, nodeLess );
317 const auto assign = [&](
NODE_ID node, uint32_t suffix )
319 auto& component = aCurrent.at( node );
322 if( !occupied.insert(
name ).second )
325 component.suffix = suffix;
326 component.name =
name;
330 for(
NODE_ID node : remaining )
332 if(
const auto* old = previous( node );
333 old && old->suffix && old->collisionBase == aCurrent.at( node ).collisionBase )
334 assign( node, old->suffix );
337 std::map<NAME_ID, uint32_t> nextSuffix;
339 for(
NODE_ID node : remaining )
345 uint32_t& suffix = nextSuffix.try_emplace( aCurrent.at( node ).baseName, 1 ).first->second;
347 while( !assign( node, suffix ) )
349 if( suffix == std::numeric_limits<uint32_t>::max() )
350 throw std::overflow_error(
"Connectivity suffix space exhausted" );
357 CHANGE_SET DescribeChanges(
const std::vector<SUCCESSION>& aGroups,
const std::set<NODE_ID>& aUnchanged,
363 const auto names = [&](
const auto& nodes,
const auto& components )
365 std::vector<NAME_ID> values;
370 values.push_back(
name );
373 std::ranges::sort( values, nameLess );
377 const auto changedNames = [&](
const auto& nodes,
const auto& before,
const auto& after )
381 const auto& previous = before.at( node );
382 const auto next = after.find( node );
384 if( previous.name !=
INVALID_ID && (
next == after.end() || previous !=
next->second ) )
385 result.netsChanged.push_back( previous.name );
389 for(
const SUCCESSION&
group : aGroups )
391 auto old = names(
group.oldNodes, aOld );
392 auto current = names(
group.newNodes, aNew );
393 changedNames(
group.oldNodes, aOld, aNew );
394 changedNames(
group.newNodes, aNew, aOld );
396 if( old.size() == 1 && current.size() == 1 )
398 if( old.front() != current.front() )
399 result.renamedNets.emplace_back( old.front(), current.front() );
401 else if( old.size() > 1 && current.size() == 1 )
403 result.mergedNets.emplace_back( std::move( old ), current.front() );
405 else if( old.size() == 1 && current.size() > 1 )
407 result.splitNets.emplace_back( old.front(), std::move( current ) );
411 result.netsRemoved.insert(
result.netsRemoved.end(), old.begin(), old.end() );
412 result.netsAdded.insert(
result.netsAdded.end(), current.begin(), current.end() );
416 for(
NODE_ID node : aUnchanged )
418 const NAME_ID old = aOld.at( node ).name;
419 const NAME_ID current = aNew.at( node ).name;
423 result.renamedNets.emplace_back( old, current );
424 result.netsChanged.push_back( old );
425 result.netsChanged.push_back( current );
429 std::ranges::sort(
result.netsAdded, nameLess );
430 std::ranges::sort(
result.netsRemoved, nameLess );
431 std::ranges::sort(
result.renamedNets, nameLess, &std::pair<NAME_ID, NAME_ID>::first );
432 std::ranges::sort(
result.mergedNets, nameLess, &std::pair<std::vector<NAME_ID>,
NAME_ID>::second );
433 std::ranges::sort(
result.splitNets, nameLess, &std::pair<
NAME_ID, std::vector<NAME_ID>>::first );
443 wxASSERT( wxThread::IsMain() );
445 std::map<NODE_ID, uint64_t> versions;
446 std::set<NODE_ID> unchanged;
448 const auto retained = [&](
NODE_ID node, uint64_t version )
450 versions.emplace( node, version );
453 if( found !=
m_versions.end() && found->second == version )
455 unchanged.insert( node );
459 return std::shared_ptr<const COMPONENT_CONTENT>();
462 for(
const auto& entry : aBundles.
Entries() )
464 auto& component = current[entry->input.anchor];
465 component.content = retained( entry->input.anchor, entry->version );
467 if( !component.content )
469 auto content = std::make_shared<COMPONENT_CONTENT>();
471 content->best = entry->value.canonical;
472 content->items = entry->value.items;
473 content->records = SortedRecords( entry->input,
m_keys );
474 content->netclasses = entry->value.netclasses;
475 content->members = entry->value.members;
476 std::ranges::transform( entry->value.slots, std::back_inserter( content->slots ), &
SLOT_INPUT::key );
479 content->baseName = content->best->fullName;
481 component.content = std::move( content );
484 for(
const SLOT_INPUT& slot : entry->value.slots )
485 slots.emplace( slot.
key, &slot );
487 component.baseName = component.content->baseName;
489 if( component.content->best )
491 const CLAIM& claim = *component.content->best;
494 throw std::invalid_argument(
"Driven bundle publication requires a schema" );
497 component.collisionBase =
504 for(
const auto& entry : aSignals.
Entries() )
506 auto& component = current[entry->input.anchor];
507 component.content = retained( entry->input.anchor, entry->version );
509 if( !component.content )
511 auto content = std::make_shared<COMPONENT_CONTENT>();
512 content->best = entry->value.summary.best;
513 content->items = entry->value.items;
514 content->slots = entry->value.slots;
515 content->nameSlot = entry->value.nameSlot;
516 content->netclasses = entry->value.summary.netclasses;
517 content->baseName = entry->value.baseName;
518 content->records = SortedRecords( entry->input,
m_keys );
519 component.content = std::move( content );
522 component.baseName = component.content->baseName;
525 PREDECESSORS predecessors;
526 const auto groups = FindSuccessions(
m_components, current, unchanged, predecessors,
m_keys );
527 const auto order = OrderedNodes( current,
m_keys );
530 for(
auto& [node, component] : current )
535 if( component.content->nameSlot )
537 const SLOT_INPUT* naming = slots.at( *component.content->nameSlot );
538 const CLAIM& best = *component.content->best;
539 const auto memberSuffix = [&](
const SLOT_INPUT& slot )
541 const auto& parent = current.at( slot.parentBundle );
542 const auto& schema = *parent.content->best->schema;
547 for(
const SLOT_KEY& key : component.content->slots )
549 const SLOT_INPUT* candidate = slots.at( key );
553 if( comparable != best )
556 if( memberSuffix( *candidate ) < memberSuffix( *naming )
557 || ( memberSuffix( *candidate ) == memberSuffix( *naming )
562 component.nameSlot = naming->
key;
563 const auto& parent = current.at( naming->
parentBundle );
564 const CLAIM& claim = *parent.content->best;
572 && old->second.content == component.content && old->second.nameSlot == component.nameSlot
573 && oldParent->second.content == parent.content && oldParent->second.suffix == parent.suffix )
575 component.baseName = old->second.baseName;
576 component.collisionBase = component.baseName;
580 wxString prefix = claim.
schema->prefix;
582 if( !prefix.IsEmpty() )
583 prefix += Suffix( parent.suffix ) +
".";
591 component.collisionBase = component.baseName;
595 std::map<NODE_ID, NODE_ID> continuations;
599 const auto pred = predecessors.find( node );
601 if( pred == predecessors.end() || !current.at( node ).content->best )
604 auto [found, inserted] = continuations.emplace( pred->second.node, node );
606 if( !inserted && pred->second.weight > predecessors.at( found->second ).weight )
607 found->second = node;
615 auto& component = current.at( node );
617 if( !component.content->best )
620 const auto pred = predecessors.find( node );
622 if( pred != predecessors.end() && continuations.at( pred->second.node ) == node )
625 component.netCode = old.netCode;
626 component.subgraphCode = old.subgraphCode;
629 if( !component.subgraphCode )
631 if( nextSubgraphCode == std::numeric_limits<uint32_t>::max() )
632 throw std::overflow_error(
"Connectivity subgraph code space exhausted" );
634 component.subgraphCode = nextSubgraphCode++;
637 if( component.content->kind ==
KIND::SIGNAL && !component.netCode )
639 if( nextNetCode == std::numeric_limits<int>::max() )
640 throw std::overflow_error(
"Connectivity net code space exhausted" );
642 component.netCode = nextNetCode++;
648 const auto collectDependents = [&](
const COMPONENTS& components )
650 std::vector<NODE_ID> pending;
651 std::set<NODE_ID> visited;
652 const auto add = [&](
NODE_ID node )
654 if( visited.insert( node ).second )
655 pending.push_back( node );
663 changedNames.insert(
name );
666 add( found->second );
669 for(
size_t i = 0; i < pending.size(); ++i )
671 const NODE_ID node = pending[i];
675 changedNames.insert(
name );
689 m_auxiliary.Update( aFrame, aInputs, aRecords, changes );
693 if(
const auto old =
m_rows.find( item ); old !=
m_rows.end() )
696 if(
const auto replacement = rows.
upserts.find( item ); replacement != rows.
upserts.end() )
697 changes.
netsChanged.push_back( replacement->second.name );
704 collectDependents( current );
705 changes.
netsChanged.assign( changedNames.begin(), changedNames.end() );
Cache current component evaluations by exact node/version identity, within one stratum and key sessio...
const auto & Entries() const
ROW_UPDATE PrepareRows(const COMPONENTS &aCurrent, const RECORD_STORE::RECORD_CACHE &aRecords, const SLOT_INPUTS &aSlots, CHANGE_SET &aChanges)
COMPONENT_LINKS m_busMembers
std::span< const NODE_ID > MembersOf(NODE_ID aBundle) const
std::map< NODE_ID, uint64_t > m_versions
SLOT_INDEX m_slotComponents
void Update(const COMPONENT_CACHE< BUNDLE_BINDING > &aBundles, const COMPONENT_CACHE< SIGNAL_RESULT > &aSignals, const RECORD_STORE::RECORD_CACHE &aRecords, std::span< const FRAME_INSTANCE > aFrame, const INPUT_STORE &aInputs)
EQUIVALENT_BUSES m_equivalentBuses
uint32_t m_nextSubgraphCode
Never reset, so codes do not repeat.
CLASS_ASSIGNMENTS m_netclasses
void ApplyRows(ROW_UPDATE &&aUpdate)
std::map< SLOT_KEY, const SLOT_INPUT *, KEY_LESS > SLOT_INPUTS
COMPONENT_LINKS m_busParents
std::map< NODE_ID, PUBLISHED_COMPONENT > COMPONENTS
int m_nextNetCode
Never reset, so codes do not repeat.
BUS_SIGNATURES m_busSignatures
void UpdateIndexes(const COMPONENTS &aCurrent, const SLOT_INPUTS &aSlots)
CACHE_TABLE< RECORD_KEY, ISLAND_RECORD, KEY_LESS > RECORD_CACHE
Session IDs are dense handles, never a canonical ordering.
bool equivalent(SIM_MODEL::DEVICE_T a, SIM_MODEL::DEVICE_T b)
Value keys and the key session of the schematic connectivity engine.
uint32_t NAME_ID
Session handle of a name, ordered by UTF-8 value through NAME_LESS.
uint32_t NODE_ID
Session handle of a NODE_KEY graph node.
KIND
The electrical type of a record or component.
constexpr uint32_t INVALID_ID
Marks an unset handle.
wxString ApplyNameSuffix(const wxString &aName, const BUS_SCHEMA *aSchema, uint32_t aSuffix)
Add "_N" to aName.
The parsed form of one bus text.
@ GROUP
A list such as I2C{SDA SCL}. Members align by name with another group.
@ VECTOR
A range such as D[0..3]. Members align by ordinal.
wxString prefix
Root prefix, such as D or I2C. It is empty for an unnamed group.
size_t prefixEnd
End of the root prefix in the escaped input text.
Difference between two publications.
std::vector< NAME_ID > netsChanged
Old and current names whose membership or presentation inputs changed, with their bus dependents.
std::vector< ITEM_KEY > changedItems
Items whose row, island, rule area, source or text changed.
Strict weak order on claims by value.
The claim of one item for the name of its island.
ITEM_KEY source
The claiming item, the last tie-break.
bool Strong() const
True for a name that the user placed.
std::shared_ptr< const BUS_SCHEMA > schema
Parsed bus, or null for a signal.
NAME_ID path
Sheet prefix, or the empty name if unscoped.
NAME_ID fullName
path followed by name.
One item or pin in one sheet instance.
Orders keys by value through SESSION_KEYS::Less().
Orders name handles by UTF-8 value.
Exact identity of one connected component.
One published net or bus with its identity.
One member position of a bus.
wxString result
Test unit parsing edge cases and error handling.