KiCad PCB EDA Suite
Loading...
Searching...
No Matches
conn_inputs.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_text.h"
21#include "conn_inputs.h"
22#include "conn_tasks.h"
23
24#include <sch_screen.h>
25#include <schematic.h>
26#include <project.h>
27#include <sch_sheet_path.h>
30#include <stdexcept>
31#include <wx/thread.h>
32#include <algorithm>
33#include <cassert>
34
35namespace SCH_CONNECTIVITY
36{
37namespace
38{
39 template <typename ROW, typename TABLE, typename KEY_OF>
40 void ReplaceRows( const std::vector<ROW>& aPrevious, const std::vector<ROW>& aCurrent, TABLE& aTable,
41 KEY_OF aKeyOf )
42 {
43 [[maybe_unused]] const auto unordered = []( const ROW& a, const ROW& b )
44 {
45 return !( a.id < b.id );
46 };
47 assert( std::adjacent_find( aPrevious.begin(), aPrevious.end(), unordered ) == aPrevious.end() );
48 assert( std::adjacent_find( aCurrent.begin(), aCurrent.end(), unordered ) == aCurrent.end() );
49
50 auto previous = aPrevious.begin();
51
52 for( const ROW& row : aCurrent )
53 {
54 for( ; previous != aPrevious.end() && previous->id < row.id; ++previous )
55 aTable.Erase( aKeyOf( previous->id ) );
56
57 const bool matched = previous != aPrevious.end() && previous->id == row.id;
58
59 if( !matched || !( *previous == row ) )
60 aTable.Set( aKeyOf( row.id ), row );
61
62 if( matched )
63 ++previous;
64 }
65
66 for( ; previous != aPrevious.end(); ++previous )
67 aTable.Erase( aKeyOf( previous->id ) );
68 }
69} // namespace
70
72 m_keys( aKeys ),
73 m_screens( aVersions ),
74 m_facts( aVersions ),
75 m_instances( aVersions ),
76 m_text( aVersions, KEY_LESS{ aKeys } ),
77 m_areas( aVersions, KEY_LESS{ aKeys } ),
78 m_geometry( aVersions ),
79 m_islands( aVersions )
80{
81}
82
83bool INPUT_STORE::ExternalSourcesChanged( const SCH_SHEET_LIST& aPaths, bool aContextUnchanged ) const
84{
85 wxASSERT( wxThread::IsMain() );
86 INPUT_TEXT_SCOPE frame;
87
88 if( TEXT_EVAL::ENVIRONMENT::Current()->IsCollectingSources() )
89 throw std::logic_error( "Cannot compare text sources during source collection" );
90
91 const bool checkReferences = !aContextUnchanged || std::ranges::any_of( aPaths,
92 [&]( const SCH_SHEET_PATH& path )
93 {
94 const SCH_SCREEN* screen = path.LastScreen();
95
96 if( !screen )
97 return false;
98
99 const auto previous = m_screenRevisions.find( screen->ConnectivityId() );
100 return previous == m_screenRevisions.end() || previous->second != screen->ConnectivityRevision();
101 } );
102
103 for( const SCH_SHEET_PATH& path : aPaths )
104 {
105 const auto instance = m_keys.FindInstance( path.Path() );
106
107 if( !instance || !path.LastScreen() )
108 continue;
109
110 const auto input = m_instanceInputs.find( *instance );
111
112 if( input == m_instanceInputs.end() || input->second.screen != path.LastScreen()->ConnectivityId() )
113 continue;
114
115 const auto* sources = Sources( *instance );
116
117 if( !sources )
118 continue;
119
120 for( const auto& [name, previous] : sources->environmentVariables )
121 {
122 wxString value;
123 const bool found = wxGetEnv( name, &value );
124
125 if( found != previous.has_value() || ( found && value != *previous ) )
126 return true;
127 }
128
129 if( sources->randomUsed )
130 return true;
131
132 if( sources->time && *sources->time != TEXT_EVAL::ENVIRONMENT::CurrentTime() )
133 return true;
134
135 if( checkReferences && !sources->crossReferences.empty() )
136 {
137 const SCHEMATIC* schematic = path.LastScreen()->Schematic();
138
139 if( !schematic )
140 return true;
141
142 std::optional<TEXT_EVAL_VCS::CONTEXT_PATH_SCOPE> vcs;
143
144 if( schematic->IsValid() )
145 {
146 if( const wxString projectPath = schematic->Project().GetProjectPath(); !projectPath.IsEmpty() )
147 vcs.emplace( projectPath );
148 }
149
150 for( const auto& [key, value] : sources->crossReferences )
151 {
152 wxString text = key.first;
153 const bool resolved = schematic->ResolveCrossReference( &text, key.second );
154
155 if( value != TEXT_EVAL::ENVIRONMENT::CROSS_REFERENCE_VALUE{ text, resolved } )
156 return true;
157 }
158 }
159
160 for( const auto& [file, hash] : sources->gitHashes )
161 {
162 const auto& current = TEXT_EVAL::ENVIRONMENT::Current()->GitHash( file, [&]
163 {
164 return KIGIT::PROJECT_GIT_UTILS::GetCurrentHash( file, false );
165 } );
166
167 if( current != hash )
168 return true;
169 }
170
171 for( const auto& [key, value] : sources->vcsValues )
172 {
173 const auto& current = TEXT_EVAL::ENVIRONMENT::Current()->VcsValue( key, [&]
174 {
175 return TEXT_EVAL_VCS::ReadSource( key );
176 } );
177
178 if( current != value )
179 return true;
180 }
181 }
182
183 return false;
184}
185
186const INPUT_STORE::SCREEN_CACHE::ENTRY& INPUT_STORE::Screen( const SCH_SCREEN& aScreen )
187{
188 wxASSERT( wxThread::IsMain() );
189 const SCREEN_ID id = aScreen.ConnectivityId();
190 const uint64_t revision = aScreen.ConnectivityRevision();
191 const auto stamp = m_screenRevisions.find( id );
192
193 if( stamp != m_screenRevisions.end() && stamp->second == revision )
194 return *m_screens.Find( id );
195
196 const auto symbolStamp = m_symbolRevisions.find( id );
197 const auto* previous = m_screens.Find( id );
198 const bool unchanged = previous && symbolStamp != m_symbolRevisions.end()
199 && symbolStamp->second == aScreen.ConnectivitySymbolRevision();
200 const auto libraries = unchanged ? previous->value.librarySymbols : nullptr;
201 return storeScreen( aScreen, ExtractScreenFacts( aScreen, libraries, unchanged ? &previous->value : nullptr ) );
202}
203
204const INPUT_STORE::SCREEN_CACHE::ENTRY& INPUT_STORE::storeScreen( const SCH_SCREEN& aScreen, SCREEN_FACTS aFacts )
205{
206 const SCREEN_ID id = aScreen.ConnectivityId();
207 const uint64_t revision = aScreen.ConnectivityRevision();
208 const auto* previous = m_screens.Find( id );
209 const SCREEN_FACTS empty;
210 const SCREEN_FACTS& old = previous ? previous->value : empty;
211 const auto keyOf = [&]( const KIID& item )
212 {
213 return SOURCE_KEY{ id, item };
214 };
215 try
216 {
217 ReplaceRows( old.items, aFacts.items, m_facts, keyOf );
218 const auto& result = m_screens.Set( id, std::move( aFacts ) );
219 m_screenRevisions.insert_or_assign( id, revision );
220 m_symbolRevisions.insert_or_assign( id, aScreen.ConnectivitySymbolRevision() );
221 return result;
222 }
223 catch( ... )
224 {
225 Clear();
226 throw;
227 }
228}
229
230const INPUT_STORE::INSTANCE_CACHE::ENTRY& INPUT_STORE::Instance( const SCH_SCREEN& aScreen, const SCH_SHEET_PATH& aPath,
231 uint64_t aTextEpoch )
232{
233 wxASSERT( wxThread::IsMain() );
234
235 if( aPath.LastScreen() != &aScreen )
236 throw std::invalid_argument( "Connectivity instance does not refer to source screen" );
237
238 const INST_ID instance = m_keys.InternInstance( aPath.Path() );
239 const SCREEN_FACTS& source = Screen( aScreen ).value;
240 const INSTANCE_INPUT input{ aScreen.ConnectivityId(), aScreen.ConnectivityRevision(), aTextEpoch,
241 aPath.GetVirtualPageNumber(), m_symbolRevisions.at( aScreen.ConnectivityId() ) };
242 const auto stamp = m_instanceInputs.find( instance );
243
244 if( stamp != m_instanceInputs.end() && stamp->second == input )
245 return *m_instances.Find( instance );
246
247 const auto* previous = m_instances.Find( instance );
248
249 // Wire-only edits keep instance text while the screen's symbol revision is unchanged
250 if( previous && stamp != m_instanceInputs.end()
251 && stamp->second.symbolRevision == input.symbolRevision && stamp->second.screen == input.screen
252 && stamp->second.textEpoch == input.textEpoch && stamp->second.pageOrder == input.pageOrder
253 && previous->value.ruleAreas.empty() && source.ruleAreas.empty() )
254 {
255 stamp->second = input;
256 return *previous;
257 }
258
259 INSTANCE_FACTS facts;
261
262 {
263 INPUT_TEXT_SCOPE inputText;
265 facts = ExtractInstanceFacts( source, aScreen, aPath );
266 }
267
268 const INSTANCE_FACTS empty;
269 const INSTANCE_FACTS& old = previous ? previous->value : empty;
270 const auto keyOf = [&]( const KIID& item )
271 {
272 return ITEM_KEY{ item, instance };
273 };
274 try
275 {
276 ReplaceRows( old.items, facts.items, m_text, keyOf );
277 ReplaceRows( old.ruleAreas, facts.ruleAreas, m_areas, keyOf );
278 const auto& result = m_instances.Set( instance, std::move( facts ) );
279 m_instanceSources.insert_or_assign( instance, std::move( sources ) );
280 m_instanceInputs.insert_or_assign( instance, input );
281 return result;
282 }
283 catch( ... )
284 {
285 Clear();
286 throw;
287 }
288}
289
290const INPUT_STORE::GEOMETRY_CACHE::ENTRY& INPUT_STORE::Geometry( SCREEN_ID aScreen )
291{
292 wxASSERT( wxThread::IsMain() );
293 const auto* source = m_screens.Find( aScreen );
294
295 if( !source )
296 throw std::invalid_argument( "Connectivity geometry requires captured screen facts" );
297
298 const auto stamp = m_geometryInputs.find( aScreen );
299
300 if( stamp != m_geometryInputs.end() && stamp->second == source->version )
301 return *m_geometry.Find( aScreen );
302
303 SCREEN_GEOMETRY geometry = GeometryOf( source->value );
304
305 try
306 {
307 const auto& result = m_geometry.Set( aScreen, std::move( geometry ) );
308 m_geometryInputs.insert_or_assign( aScreen, source->version );
309 return result;
310 }
311 catch( ... )
312 {
313 Clear();
314 throw;
315 }
316}
317
318const INPUT_STORE::ISLAND_CACHE::ENTRY& INPUT_STORE::Islands( SCREEN_ID aScreen, const UNIT_SIGNATURE& aUnits )
319{
320 wxASSERT( wxThread::IsMain() );
321
322 const auto& geometry = Geometry( aScreen );
323 const ISLAND_LOOKUP lookup{ aScreen, aUnits };
324 const auto stamp = m_islandInputs.find( lookup );
325
326 if( stamp != m_islandInputs.end() && stamp->second == geometry.version )
327 return *m_islands.Find( lookup );
328
329 SCREEN_ISLANDS islands = BuildScreenIslands( geometry.value, aUnits );
330
331 try
332 {
333 const ISLAND_KEY key{ aScreen, aUnits };
334 const auto& result = m_islands.Set( key, std::move( islands ) );
335 m_islandInputs.insert_or_assign( key, geometry.version );
336 return result;
337 }
338 catch( ... )
339 {
340 Clear();
341 throw;
342 }
343}
344
345std::vector<FRAME_INSTANCE> INPUT_STORE::Capture( const SCH_SHEET_LIST& aPaths, uint64_t aTextEpoch )
346{
347 wxASSERT( wxThread::IsMain() );
348
349 INPUT_TEXT_SCOPE inputText;
350 auto frame = CaptureHierarchy( aPaths, m_keys );
351 std::map<INST_ID, const SCH_SHEET_PATH*> paths;
352
353 for( const SCH_SHEET_PATH& path : aPaths )
354 {
355 if( !path.empty() && path.LastScreen() )
356 paths.emplace( m_keys.InternInstance( path.Path() ), &path );
357 }
358
359 std::set<SCREEN_ID> screens;
360 std::set<INST_ID> instances;
361
362 for( const FRAME_INSTANCE& item : frame )
363 {
364 const SCH_SHEET_PATH& path = *paths.at( item.scope.instance );
365 Instance( *path.LastScreen(), path, aTextEpoch );
366 screens.insert( item.screen );
367 instances.insert( item.scope.instance );
368 }
369
370 struct ISLAND_JOB
371 {
372 ISLAND_KEY key;
373 const GEOMETRY_CACHE::ENTRY* geometry;
374 };
375 std::vector<ISLAND_JOB> jobs;
376 std::set<ISLAND_KEY, ISLAND_LESS> seen;
377
378 for( const FRAME_INSTANCE& item : frame )
379 {
380 const auto& units = m_instances.Find( item.scope.instance )->value.units;
381 ISLAND_KEY key{ item.screen, units };
382
383 if( !seen.insert( key ).second )
384 continue;
385
386 const auto& geometry = Geometry( item.screen );
387 const auto stamp = m_islandInputs.find( key );
388
389 if( stamp == m_islandInputs.end() || stamp->second != geometry.version )
390 jobs.push_back( { std::move( key ), &geometry } );
391 }
392
393 std::vector<SCREEN_ISLANDS> results( jobs.size() );
394
395 try
396 {
397 ParallelFor( jobs.size(), [&]( size_t i )
398 {
399 results[i] = BuildScreenIslands( jobs[i].geometry->value, jobs[i].key.second );
400 } );
401
402 for( size_t i = 0; i < jobs.size(); ++i )
403 {
404 m_islands.Set( jobs[i].key, std::move( results[i] ) );
405 m_islandInputs.insert_or_assign( jobs[i].key, jobs[i].geometry->version );
406 }
407 }
408 catch( ... )
409 {
410 Clear();
411 throw;
412 }
413
414 Retain( screens, instances );
415 return frame;
416}
417
418
420{
421 wxASSERT( wxThread::IsMain() );
422 m_screenRevisions.clear();
423 m_symbolRevisions.clear();
424 m_instanceInputs.clear();
425 m_instanceSources.clear();
426 m_geometry.Clear();
427 m_islands.Clear();
428 m_geometryInputs.clear();
429 m_islandInputs.clear();
430}
431
433{
434 Invalidate();
435 m_screens.Clear();
436 m_facts.Clear();
437 m_instances.Clear();
438 m_text.Clear();
439 m_areas.Clear();
440}
441
442void INPUT_STORE::Retain( const std::set<SCREEN_ID>& aScreens, const std::set<INST_ID>& aInstances )
443{
444 wxASSERT( wxThread::IsMain() );
445 // Views refer only to retained instances; their unit vectors remain unchanged
446 std::set<ISLAND_LOOKUP, ISLAND_LESS> liveIslands;
447
448 for( auto it = m_instances.Entries().begin(); it != m_instances.Entries().end(); )
449 {
450 const INST_ID id = it->first;
451 const auto input = m_instanceInputs.find( id );
452
453 if( aInstances.contains( id ) && input != m_instanceInputs.end() && aScreens.contains( input->second.screen ) )
454 {
455 liveIslands.emplace( input->second.screen, it->second->value.units );
456 ++it;
457 continue;
458 }
459
460 for( const auto& row : it->second->value.items )
461 m_text.Erase( { row.id, id } );
462
463 for( const auto& row : it->second->value.ruleAreas )
464 m_areas.Erase( { row.id, id } );
465
466 ++it;
467 m_instances.Erase( id );
468 m_instanceInputs.erase( id );
469 m_instanceSources.erase( id );
470 }
471
472 for( auto it = m_screens.Entries().begin(); it != m_screens.Entries().end(); )
473 {
474 const SCREEN_ID id = it->first;
475
476 if( aScreens.contains( id ) )
477 {
478 ++it;
479 continue;
480 }
481
482 for( const auto& row : it->second->value.items )
483 m_facts.Erase( { id, row.id } );
484
485 ++it;
486 m_screens.Erase( id );
487 m_screenRevisions.erase( id );
488 m_symbolRevisions.erase( id );
489 m_geometry.Erase( id );
490 m_geometryInputs.erase( id );
491 }
492
493 for( auto it = m_islands.Entries().begin(); it != m_islands.Entries().end(); )
494 {
495 if( liveIslands.contains( it->first ) )
496 {
497 ++it;
498 continue;
499 }
500
501 const auto removed = it++;
502 m_islandInputs.erase( removed->first );
503 m_islands.Erase( removed->first );
504 }
505}
506} // namespace SCH_CONNECTIVITY
const char * name
static wxString GetCurrentHash(const wxString &aProjectFile, bool aShort)
Return the current HEAD commit hash for the repository containing aProjectFile.
Definition kiid.h:46
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
Holds all the data relating to one schematic.
Definition schematic.h:149
PROJECT & Project() const
Return a reference to the project this schematic is part of.
Definition schematic.h:171
bool IsValid() const
A simple test if the schematic is loaded, not a complete one.
Definition schematic.h:289
bool ResolveCrossReference(wxString *token, int aDepth) const
Resolves text vars that refer to other items.
One sequence for all cache tables for the lifetime of an engine session.
Definition conn_cache.h:37
std::map< SCREEN_ID, uint64_t > m_screenRevisions
INPUT_STORE(CACHE_VERSIONS &aVersions, SESSION_KEYS &aKeys)
std::map< INST_ID, TEXT_EVAL::ENVIRONMENT::SOURCE_VALUES > m_instanceSources
const ISLAND_CACHE::ENTRY & Islands(SCREEN_ID aScreen, const UNIT_SIGNATURE &aUnits)
std::pair< SCREEN_ID, UNIT_VIEW > ISLAND_LOOKUP
Definition conn_inputs.h:52
void Retain(const std::set< SCREEN_ID > &aScreens, const std::set< INST_ID > &aInstances)
const SCREEN_CACHE::ENTRY & Screen(const SCH_SCREEN &aScreen)
std::map< ISLAND_KEY, uint64_t, ISLAND_LESS > m_islandInputs
const INSTANCE_CACHE::ENTRY & Instance(const SCH_SCREEN &aScreen, const SCH_SHEET_PATH &aPath, uint64_t aTextEpoch)
std::map< INST_ID, INSTANCE_INPUT > m_instanceInputs
const GEOMETRY_CACHE::ENTRY & Geometry(SCREEN_ID aScreen)
const TEXT_EVAL::ENVIRONMENT::SOURCE_VALUES * Sources(INST_ID aInstance) const
std::vector< FRAME_INSTANCE > Capture(const SCH_SHEET_LIST &aPaths, uint64_t aTextEpoch)
void Invalidate()
Force fresh extraction and geometry, retaining primitive values only for change comparison.
std::vector< std::pair< KIID, int > > UNIT_SIGNATURE
Definition conn_inputs.h:49
std::pair< SCREEN_ID, UNIT_SIGNATURE > ISLAND_KEY
Definition conn_inputs.h:50
std::pair< SCREEN_ID, KIID > SOURCE_KEY
Definition conn_inputs.h:43
std::map< SCREEN_ID, uint64_t > m_geometryInputs
const SCREEN_CACHE::ENTRY & storeScreen(const SCH_SCREEN &aScreen, SCREEN_FACTS aFacts)
bool ExternalSourcesChanged(const SCH_SHEET_LIST &aPaths, bool aContextUnchanged=false) const
Compare retained external inputs for current instance/screen pairs, excluding model/revision changes.
std::map< SCREEN_ID, uint64_t > m_symbolRevisions
Excludes derived graph text and shares dynamic source values through nested resolvers.
Definition conn_text.h:34
Session IDs are dense handles, never a canonical ordering.
Definition conn_keys.h:146
uint64_t ConnectivityId() const
Process-local lifetime identity; file UUIDs can be shared by distinct screens.
Definition sch_screen.h:201
uint64_t ConnectivityRevision() const
The only change signal that the connectivity engine reads from this screen.
Definition sch_screen.h:181
uint64_t ConnectivitySymbolRevision() const
Changes with every bump except a wire-only bump, so wire edits keep cached text.
Definition sch_screen.h:184
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
SCH_SCREEN * LastScreen()
int GetVirtualPageNumber() const
static wxDateTime CurrentTime()
const VCS_VALUE & VcsValue(const VCS_KEY &aKey, const std::function< VCS_VALUE()> &aRead)
const wxString & GitHash(const wxString &aPath, const std::function< wxString()> &aRead)
Return the value memoized for this frame.
static ENVIRONMENT * Current()
Record every source read through aEnvironment into aValues, including memo hits.
static bool empty(const wxTextEntryBase *aCtrl)
Value keys and the key session of the schematic connectivity engine.
INSTANCE_FACTS ExtractInstanceFacts(const SCREEN_FACTS &aFacts, const SCH_SCREEN &aScreen, const SCH_SHEET_PATH &aPath)
uint32_t INST_ID
Session handle of a sheet instance KIID_PATH.
Definition conn_keys.h:41
void ParallelFor(size_t aCount, FUNCTION &&aFunction, thread_pool &aPool=GetKiCadThreadPool())
Independent ordinal writes only; preparation and cache commits stay on the caller thread.
Definition conn_tasks.h:72
uint64_t SCREEN_ID
Process-local SCH_SCREEN::ConnectivityId(), never a file UUID.
Definition conn_keys.h:44
SCREEN_GEOMETRY GeometryOf(const SCREEN_FACTS &aFacts)
SCREEN_FACTS ExtractScreenFacts(const SCH_SCREEN &aScreen, std::shared_ptr< const LIBRARY_SYMBOL_FACTS > aLibrarySymbols, const SCREEN_FACTS *aUnchangedNonLines)
Main-thread snapshots; no derived connectivity or model pointers survive extraction.
SCREEN_ISLANDS BuildScreenIslands(const SCREEN_GEOMETRY &aFacts, const std::vector< std::pair< KIID, int > > &aUnits)
Kindless geometry for one unit signature.
std::vector< FRAME_INSTANCE > CaptureHierarchy(const SCH_SHEET_LIST &aPaths, SESSION_KEYS &aKeys)
Interns every sheet instance and returns the frame in KIID_PATH order, so each parent precedes its ch...
TEXT_EVAL::ENVIRONMENT::VCS_VALUE ReadSource(const TEXT_EVAL::ENVIRONMENT::VCS_KEY &aKey)
Re-read an owned query descriptor using any active frame memo.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
One entry of the captured hierarchy.
Definition conn_frame.h:48
The text and unit data that one sheet instance resolves for the items of its screen.
Definition conn_facts.h:348
std::vector< INSTANCE_RULE_AREA_FACT > ruleAreas
Definition conn_facts.h:356
std::vector< ITEM_TEXT_FACT > items
Definition conn_facts.h:355
One item or pin in one sheet instance.
Definition conn_keys.h:77
Orders keys by value through SESSION_KEYS::Less().
Definition conn_keys.h:255
The value copy of one screen.
Definition conn_facts.h:251
std::vector< ITEM_FACT > items
Definition conn_facts.h:256
std::vector< RULE_AREA_FACT > ruleAreas
Definition conn_facts.h:257
The islands of one screen and unit signature, sorted by anchor.
std::string path
wxString result
Test unit parsing edge cases and error handling.