KiCad PCB EDA Suite
Loading...
Searching...
No Matches
conn_rows.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "conn_publish.h"
21#include <sch_connection.h>
22#include <algorithm>
23#include <numeric>
24#include <set>
25#include <tuple>
26
27namespace SCH_CONNECTIVITY
28{
29namespace
30{
31 template<typename T>
32 bool SameValue( const std::shared_ptr<const T>& a, const std::shared_ptr<const T>& b )
33 {
34 return a == b || ( a && b && *a == *b );
35 }
36} // namespace
37
39{
40 return source == aOther.source && SameValue( schema, aOther.schema );
41}
42
43bool ITEM_RESULT::operator==( const ITEM_RESULT& aOther ) const
44{
46 == std::tie( aOther.component, aOther.kind, aOther.itemType, aOther.name, aOther.localName,
47 aOther.fullLocalName, aOther.driver, aOther.netCode, aOther.subgraphCode )
48 && SameValue( netclasses, aOther.netclasses ) && SameValue( busSource, aOther.busSource );
49}
50
52 m_keys( aKeys ),
53 m_auxiliary( aKeys ),
54 m_rows( KEY_LESS{ aKeys } ),
55 m_rowInputs( KEY_LESS{ aKeys } ),
56 m_netclasses( NAME_LESS{ &aKeys } ),
57 m_byName( NAME_LESS{ &aKeys } ),
58 m_slotComponents( KEY_LESS{ aKeys } )
59{
60}
61
63 const RECORD_STORE::RECORD_CACHE& aRecords,
64 const SLOT_INPUTS& aSlots, CHANGE_SET& aChanges )
65{
67 std::set<RECORD_KEY, KEY_LESS> removedInputs( KEY_LESS{ m_keys } );
68 const CLAIM_LESS less{ m_keys };
69 const auto sameComponent = []( const PUBLISHED_COMPONENT& a, const PUBLISHED_COMPONENT& b )
70 {
71 // Equal aggregate values can still hide changed record-local claims
72 return a.content == b.content && a == b;
73 };
74
75 for( const auto& [node, component] : m_components )
76 {
77 const auto current = aCurrent.find( node );
78
79 if( current == aCurrent.end() || !sameComponent( component, current->second ) )
80 removedInputs.insert( component.content->records.begin(), component.content->records.end() );
81 }
82
83 for( const auto& [node, component] : aCurrent )
84 {
85 ITEM_RESULT connection;
86 connection.component = node;
87 connection.kind = component.content->kind;
89 connection.name = component.name;
90 connection.netCode = component.netCode;
91 connection.subgraphCode = component.subgraphCode;
92
93 if( !component.content->netclasses.empty() )
94 {
95 // The netclass map orders by name text, which an unnamed component does not have
96 const auto old = component.name == INVALID_ID ? m_netclasses.end() : m_netclasses.find( component.name );
97 connection.netclasses =
98 old != m_netclasses.end() && *old->second == component.content->netclasses
99 ? old->second
100 : std::make_shared<const std::vector<NAME_ID>>( component.content->netclasses );
101 }
102
103 if( component.content->best )
104 {
105 connection.driver = component.content->best->source;
106 connection.itemType = component.content->kind == KIND::SIGNAL ? CONNECTION_TYPE::NET
107 : component.content->best->schema->shape == BUS_SCHEMA::SHAPE::VECTOR
110 connection.localName =
111 component.nameSlot ? aSlots.at( *component.nameSlot )->localName : component.content->best->name;
112 }
113
114 if( component.name != INVALID_ID && !component.content->netclasses.empty() )
115 result.netclasses.emplace( component.name, connection.netclasses );
116
117 const auto previous = m_components.find( node );
118
119 if( previous != m_components.end() && sameComponent( previous->second, component ) )
120 continue;
121
122 // Islands on one sheet merge only through a shared driver name, which their sheet edges record
123 const auto& records = component.content->records;
124 std::vector<size_t> parents( records.size() );
125 std::vector<std::pair<size_t, const CLAIM*>> groups( records.size() );
126 std::iota( parents.begin(), parents.end(), size_t( 0 ) );
127
128 const auto root = [&]( size_t aIndex )
129 {
130 while( parents[aIndex] != aIndex )
131 aIndex = parents[aIndex] = parents[parents[aIndex]];
132
133 return aIndex;
134 };
135
136 if( component.content->kind == KIND::SIGNAL )
137 {
138 std::map<std::pair<INST_ID, NAME_ID>, size_t> names;
139
140 for( size_t index = 0; index < records.size(); ++index )
141 {
142 const auto* record = aRecords.Find( records[index] );
143
144 if( !record )
145 continue;
146
147 for( const NAME_KEY& edge : record->value.edges )
148 {
149 if( edge.scope != SCOPE::SHEET )
150 continue;
151
152 const auto [named, inserted] = names.try_emplace( { edge.inst, edge.text }, index );
153
154 if( !inserted )
155 parents[root( index )] = root( named->second );
156 }
157 }
158
159 for( size_t index = 0; index < records.size(); ++index )
160 {
161 const auto* record = aRecords.Find( records[index] );
162
163 if( !record || record->value.claims.empty() )
164 continue;
165
166 auto& [count, best] = groups[root( index )];
167 ++count;
168
169 if( !best || less( *best, record->value.claims.front() ) )
170 best = &record->value.claims.front();
171 }
172 }
173
174 for( size_t index = 0; index < records.size(); ++index )
175 {
176 const RECORD_KEY& key = records[index];
177 removedInputs.erase( key );
178 const auto* record = aRecords.Find( key );
179
180 if( !record || record->value.kind != component.content->kind )
181 throw std::invalid_argument( "Published component does not match its row records" );
182
183 const auto old = m_rowInputs.find( key );
184 ITEM_RESULT local = connection;
185 const CLAIM* localClaim = component.content->best ? &*component.content->best : nullptr;
186
187 if( !record->value.claims.empty() )
188 {
189 localClaim = &record->value.claims.front();
190 local.localName = localClaim->name;
191
192 if( record->value.claims.front().priority == PRIORITY::PIN && component.content->best
193 && component.content->best->priority == PRIORITY::PIN )
194 {
195 local.localName = component.baseName;
196 localClaim = &*component.content->best;
197 }
198 }
199
200 // Items of a merged group take the winner's local name, but each island driver keeps its own
201 NAME_ID driverName = INVALID_ID;
202
203 if( !record->value.claims.empty() && localClaim == &record->value.claims.front() )
204 {
205 const auto& [count, winner] = groups[root( index )];
206
207 if( count > 1 && winner->name != localClaim->name )
208 {
209 driverName = local.localName;
210 local.localName = winner->name;
211 }
212 }
213
214 if( local.kind == KIND::BUNDLE && localClaim && localClaim->schema && component.content->best
215 && localClaim->schema->shape == component.content->best->schema->shape
216 && localClaim->name != component.content->best->name )
217 {
218 const ITEM_RESULT::BUS_SOURCE source{ localClaim->source, localClaim->schema };
219 const auto previousSource = old != m_rowInputs.end() ? old->second->connection.busSource : nullptr;
220 local.busSource = previousSource && *previousSource == source
221 ? previousSource
222 : std::make_shared<const ITEM_RESULT::BUS_SOURCE>( source );
223 }
224
225 // Cached row inputs and components always describe the same previous publication
226 if( local.localName == INVALID_ID || !component.suffix )
227 local.fullLocalName = local.localName;
228 else if( old != m_rowInputs.end() && old->second->version == record->version
229 && old->second->connection.localName == local.localName
230 && old->second->connection.itemType == local.itemType
231 && m_components.at( old->second->connection.component ).suffix == component.suffix )
232 local.fullLocalName = old->second->connection.fullLocalName;
233 else
234 local.fullLocalName = m_keys.InternName(
235 ApplyNameSuffix( m_keys.Name( local.localName ),
236 localClaim ? localClaim->schema.get() : nullptr, component.suffix ) );
237
238 std::optional<std::pair<KIID, ITEM_RESULT>> driver;
239
240 if( driverName != INVALID_ID )
241 {
242 ITEM_RESULT row = local;
243 row.localName = driverName;
244 row.fullLocalName = driverName;
245
246 if( component.suffix )
247 {
248 row.fullLocalName = m_keys.InternName(
249 ApplyNameSuffix( m_keys.Name( driverName ), nullptr, component.suffix ) );
250 }
251
252 driver.emplace( localClaim->source.item, std::move( row ) );
253 }
254
255 if( old == m_rowInputs.end() || old->second->version != record->version
256 || old->second->connection != local || old->second->driver != driver )
257 {
258 result.inputs.emplace( key, std::make_shared<const ROW_INPUT>( ROW_INPUT{
259 record->version, local, record->value.items,
260 std::move( driver ) } ) );
261 }
262 }
263 }
264
265 std::set<ITEM_KEY, KEY_LESS> removed( KEY_LESS{ m_keys } );
266 const auto removePreviousItems = [&]( const RECORD_KEY& key )
267 {
268 const auto old = m_rowInputs.find( key );
269
270 if( old != m_rowInputs.end() )
271 {
272 for( const KIID& item : old->second->items )
273 removed.insert( { item, key.inst } );
274 }
275 };
276 result.removedInputs.assign( removedInputs.begin(), removedInputs.end() );
277
278 for( const RECORD_KEY& key : result.removedInputs )
279 removePreviousItems( key );
280
281 for( const auto& [key, input] : result.inputs )
282 removePreviousItems( key );
283
284 for( const auto& [key, replacement] : result.inputs )
285 {
286 const ROW_INPUT& input = *replacement;
287
288 for( const KIID& id : input.items )
289 {
290 const ITEM_RESULT& row = input.driver && input.driver->first == id ? input.driver->second
291 : input.connection;
292 const ITEM_KEY item{ id, key.inst };
293
294 removed.erase( item );
295 const auto old = m_rows.find( item );
296
297 if( old == m_rows.end() || old->second != row )
298 {
299 aChanges.changedItems.push_back( item );
300
301 if( old == m_rows.end() ? row.driver.has_value() : old->second.driver != row.driver )
302 aChanges.driverChangedItems.push_back( item );
303
304 result.upserts.emplace( item, row );
305 }
306 }
307 }
308
309 result.removed.assign( removed.begin(), removed.end() );
310
311 for( const ITEM_KEY& item : result.removed )
312 {
313 const auto old = m_rows.find( item );
314
315 if( old != m_rows.end() )
316 {
317 aChanges.changedItems.push_back( item );
318
319 if( old->second.driver )
320 aChanges.driverChangedItems.push_back( item );
321 }
322 }
323
324 std::ranges::sort( aChanges.driverChangedItems, KEY_LESS{ m_keys } );
325
326 for( const auto& [name, classes] : result.netclasses )
327 {
328 const auto old = m_netclasses.find( name );
329
330 if( old == m_netclasses.end() || !SameValue( old->second, classes ) )
331 aChanges.netclasses.assigned.emplace_back( name, *classes );
332 }
333
334 for( const auto& [name, classes] : m_netclasses )
335 {
336 if( !result.netclasses.contains( name ) )
337 aChanges.netclasses.removed.push_back( name );
338 }
339
340 std::ranges::sort( aChanges.netclasses.removed, NAME_LESS{ &m_keys } );
341 return result;
342}
343
345{
346 // Merge splices new keys and leaves existing ones behind to be assigned
347 const auto upsert = []( auto& aTarget, auto& aSource )
348 {
349 aTarget.merge( aSource );
350
351 for( auto& [key, value] : aSource )
352 aTarget.find( key )->second = std::move( value );
353 };
354
355 for( const ITEM_KEY& item : aUpdate.removed )
356 m_rows.erase( item );
357
358 upsert( m_rows, aUpdate.upserts );
359
360 for( const RECORD_KEY& key : aUpdate.removedInputs )
361 m_rowInputs.erase( key );
362
363 upsert( m_rowInputs, aUpdate.inputs );
364 m_netclasses = std::move( aUpdate.netclasses );
365}
366} // namespace SCH_CONNECTIVITY
int index
const char * name
Definition kiid.h:46
const ENTRY * Find(const KEY &aKey) const
Entry references survive unchanged writes, but not replacement or erasure of their key.
Definition conn_cache.h:80
ROW_UPDATE PrepareRows(const COMPONENTS &aCurrent, const RECORD_STORE::RECORD_CACHE &aRecords, const SLOT_INPUTS &aSlots, CHANGE_SET &aChanges)
Definition conn_rows.cpp:62
PUBLICATION(SESSION_KEYS &aKeys)
Definition conn_rows.cpp:51
CLASS_ASSIGNMENTS m_netclasses
void ApplyRows(ROW_UPDATE &&aUpdate)
std::map< SLOT_KEY, const SLOT_INPUT *, KEY_LESS > SLOT_INPUTS
std::map< NODE_ID, PUBLISHED_COMPONENT > COMPONENTS
CACHE_TABLE< RECORD_KEY, ISLAND_RECORD, KEY_LESS > RECORD_CACHE
Session IDs are dense handles, never a canonical ordering.
Definition conn_keys.h:146
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.
Definition conn_keys.h:42
constexpr uint32_t INVALID_ID
Marks an unset handle.
Definition conn_keys.h:47
@ PIN
Symbol pin that is not a power input. The name is "Net-(...)".
Definition conn_claims.h:39
wxString ApplyNameSuffix(const wxString &aName, const BUS_SCHEMA *aSchema, uint32_t aSuffix)
Add "_N" to aName.
@ BUS
This item represents a bus vector.
@ NET
This item represents a net.
@ NONE
No connection to this item.
@ BUS_GROUP
This item represents a bus group.
@ VECTOR
A range such as D[0..3]. Members align by ordinal.
Definition conn_bus.h:51
Difference between two publications.
std::vector< ITEM_KEY > driverChangedItems
Items whose ITEM_RESULT::driver changed or was removed.
std::vector< ITEM_KEY > changedItems
Items whose row, island, rule area, source or text changed.
Strict weak order on claims by value.
Definition conn_claims.h:77
The claim of one item for the name of its island.
Definition conn_claims.h:53
ITEM_KEY source
The claiming item, the last tie-break.
Definition conn_claims.h:60
NAME_ID name
Item text without the sheet prefix.
Definition conn_claims.h:57
std::shared_ptr< const BUS_SCHEMA > schema
Parsed bus, or null for a signal.
Definition conn_claims.h:65
One item or pin in one sheet instance.
Definition conn_keys.h:77
KIID item
The item or pin KIID.
Definition conn_keys.h:78
Bus claim of an island that has the canonical shape but a different name.
bool operator==(const BUS_SOURCE &aOther) const
Definition conn_rows.cpp:38
std::shared_ptr< const BUS_SCHEMA > schema
Published connection of one item on one sheet instance.
std::shared_ptr< const std::vector< NAME_ID > > netclasses
std::shared_ptr< const BUS_SOURCE > busSource
NAME_ID localName
Best island claim, or the group winner.
NAME_ID fullLocalName
localName with the component suffix.
bool operator==(const ITEM_RESULT &aOther) const
Definition conn_rows.cpp:43
NAME_ID name
Published name with its suffix.
std::optional< ITEM_KEY > driver
Source of the best component claim.
Orders keys by value through SESSION_KEYS::Less().
Definition conn_keys.h:255
The graph node of one name in one scope.
Definition conn_keys.h:110
INST_ID inst
The sheet instance, or the child instance of a sheet pin for PORT.
Definition conn_keys.h:112
NAME_ID text
The resolved name.
Definition conn_keys.h:113
Orders name handles by UTF-8 value.
Definition conn_keys.h:246
std::vector< NAME_ID > removed
Names that no longer have netclasses.
std::vector< std::pair< NAME_ID, std::vector< NAME_ID > > > assigned
Names whose netclass list is new or different, in name order, with the new list.
Shared row of one island record.
std::optional< std::pair< KIID, ITEM_RESULT > > driver
ITEM_RESULT connection
Row of every island item except the driver override.
One published net or bus with its identity.
std::shared_ptr< const COMPONENT_CONTENT > content
One island in one sheet instance.
Definition conn_keys.h:87
INST_ID inst
The sheet instance of the record.
Definition conn_keys.h:88
wxString result
Test unit parsing edge cases and error handling.