| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
Manually update other schemas | 1 个月前 | |
[AI-generated, unverified] ifcparse: stop scanning a type's instance list when deleting an instance (#9520) * ifcparse: keep each type's instance list sorted by id remove_type_ref() ran std::remove over every instance of the deleted instance's type, comparing express::base handles (a data() dereference each): O(instances of the type) per deletion, and the bulk of what file.remove() still cost after the inverse index fixes. Keep the per-type lists sorted by id instead. Loading fills them in file order and sort_type_lists() establishes the order once: a list already in id order, the common case, is only checked; otherwise the ids are read once and (id, position) pairs are sorted, the lists in parallel, so a file that isn't in id order costs a few tens of milliseconds to open rather than a sort that dereferences every comparison. add_type_ref() appends a fresh id or inserts an explicit one in place; remove_type_ref() finds the instance by binary search and erases it, a memmove of handles with no dereferences. Nothing about the lists is hidden from their readers, and by_type() now returns instances in id order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: sort the type lists on the loading thread A library has no business sizing a thread pool by hardware_concurrency() on its own; how many threads a file open may use is a caller's decision. Until that is configurable the lists are sorted serially. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 9 天前 | |
Point references to the v0.8.0 branch to v0.9.0 v0.9.0 is the default branch now. - The AI chat app and pyodide demo app workflows still only published on pushes to v0.8.0, unlike every other publish workflow. - Docs "edit this page" links (source_branch in the three Sphinx configs), repository links in docs, CITATION.cff files and the choco licence URL. All link targets were checked to exist on v0.9.0. The translations docs pointed to scripts/bbim_translations.py, which was renamed to bonsai_translations.py. - The bSDD client User-Agent. - pyodide README no longer names a versioned build folder. Left alone as they record what produced a file or pin an artifact that exists: IFC fixture headers, wasm wheel URLs, the ifcsverchok-0.8.2 download, the AWS Dockerfile build id, conda's "v0.8.0 Stable" and the old builds list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gpj1MBesJMgsZnupzFqjtE | 14 天前 | |
cmake - option to build only common schemas | 5 天前 | |
Modifications to build C++ with Visual Studio 2026 and the v145 toolset. (#9359) * Modifications to build C++ with Visual Studio 2026 and the v145 toolset. * Fixes linker settings for rocksdb for Debug and Release builds * module is a C++ 20 keyword. Explicitly stating namespace allows cpp20 projects to build against the library * Fixes crash when initializing an object with the initialize function when some of the attributes are empty, {}, or omitted, std::nullopt * cleanup for vs2026 v145 toolset per @aothms review * Fixes bug, IfcCurveSegment.setStartLength was setSegmentLength in alignment_helper.cpp * Bumps boost to 1.92 | 1 个月前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
Replace Boost shared pointers Generated with the assistance of an AI coding tool. | 1 个月前 | |
ifcparse: keep unresolved references in the attribute slots The parser could not resolve a #name when it read it, because the instance may be defined further down the file, so it left the slot empty and appended (owner, attribute, name) to a side table that a second pass walked. The table held one entry per reference for the whole read: 64 MB on a 58 MB model, the high-water mark of opening. Now the reference stays where the tokenizer put it: the attribute slot holds the instance_reference, or the reference_or_simple_type aggregate for a list (mixed with inline typed values or not), until every instance has been read, and resolve_instance_references() walks each instance's slots and swaps names for instances. Ordering is what the tokenizer produced; nothing is re-derived. A missing name becomes null in a scalar and is dropped from an aggregate, as before; the error keeps its offset. The three transient alternatives are appended to the attribute pack and to argument_type in lock step and are never visible once a file is loaded. Simple type instances read inline (IfcPropertySetDefinitionSet) have their own slots, so their references need no diversion. The table remains for the header entities and for streaming consumers of instance_streamer::references(), which leave resolve_references_in_place off. TXG 58 MB / 210_King 147 MB / OKgate22 231 MB, single thread: time unchanged (1.05 / 2.69 / 4.99 s), memory after the parse 287 -> 274, 698 -> 654, 1086 -> 1036 MB, peak 400 -> 365, 965 -> 871, 1471 -> 1347 MB. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
cmake: drop the unreachable hardcoded version fallback buildinfo.cpp fell back to a hardcoded "0.8.0" when neither the commit sha nor IFCOPENSHELL_VERSION_STRING was defined. CMake defines the latter for IfcParse unconditionally since #8164 and nothing else compiles the file, so the branch could not be reached and only left a version string behind to go stale. The two CMake comments described that fix (the fallback they mention was the `set(RELEASE_VERSION "0.8.0")` removed in the same commit) rather than the code. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gpj1MBesJMgsZnupzFqjtE | 14 天前 | |
ifcparse: give the tokenizer a compile-time policy for what it decodes spf_lexer::next() becomes next<Policy>(). full_tokens, the default, is what the parser has always had. index_tokens is what the lazy index needs: a string is ended but not decoded, and a number, enumeration or binary comes back as Token_LITERAL with only its position; names, keywords and operators are read as before. Each policy compiles to its own loop from the one implementation, so there is no second tokenizer. character_decoder gains skip(): the same state machine as the conversion with the collection compiled out, so an escape such as \S\' (an apostrophe as the page character) ends the string at the same byte under both policies. A byte-level scan would have ended it early. Also fixes a comment that follows a token without whitespace, ",/* x */", which skip_comment() never saw because the slash had been consumed. TXG (58 MB), single thread: tokenizing the whole file 194 MB/s with full_tokens, 249 MB/s with index_tokens; through 64 KB pages 196 and 205 MB/s. The parse itself is unchanged. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
ifcparse: give the tokenizer a compile-time policy for what it decodes spf_lexer::next() becomes next<Policy>(). full_tokens, the default, is what the parser has always had. index_tokens is what the lazy index needs: a string is ended but not decoded, and a number, enumeration or binary comes back as Token_LITERAL with only its position; names, keywords and operators are read as before. Each policy compiles to its own loop from the one implementation, so there is no second tokenizer. character_decoder gains skip(): the same state machine as the conversion with the collection compiled out, so an escape such as \S\' (an apostrophe as the page character) ends the string at the same byte under both policies. A byte-level scan would have ended it early. Also fixes a comment that follows a token without whitespace, ",/* x */", which skip_comment() never saw because the slash had been consumed. TXG (58 MB), single thread: tokenizing the whole file 194 MB/s with full_tokens, 249 MB/s with index_tokens; through 64 KB pages 196 and 205 MB/s. The parse itself is unchanged. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
ifcparse: keep unresolved references in the attribute slots The parser could not resolve a #name when it read it, because the instance may be defined further down the file, so it left the slot empty and appended (owner, attribute, name) to a side table that a second pass walked. The table held one entry per reference for the whole read: 64 MB on a 58 MB model, the high-water mark of opening. Now the reference stays where the tokenizer put it: the attribute slot holds the instance_reference, or the reference_or_simple_type aggregate for a list (mixed with inline typed values or not), until every instance has been read, and resolve_instance_references() walks each instance's slots and swaps names for instances. Ordering is what the tokenizer produced; nothing is re-derived. A missing name becomes null in a scalar and is dropped from an aggregate, as before; the error keeps its offset. The three transient alternatives are appended to the attribute pack and to argument_type in lock step and are never visible once a file is loaded. Simple type instances read inline (IfcPropertySetDefinitionSet) have their own slots, so their references need no diversion. The table remains for the header entities and for streaming consumers of instance_streamer::references(), which leave resolve_references_in_place off. TXG 58 MB / 210_King 147 MB / OKgate22 231 MB, single thread: time unchanged (1.05 / 2.69 / 4.99 s), memory after the parse 287 -> 274, 698 -> 654, 1086 -> 1036 MB, peak 400 -> 365, 965 -> 871, 1471 -> 1347 MB. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
Restructure and rename | 6 个月前 | |
Parameter naming | 6 个月前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
fix(#6032): guard express::base::as<T>() on null instance | 1 个月前 | |
[AI-generated, unverified] ifcparse: make deleting and creating instances work on RocksDB-backed files (#9508) * ifcparse: make deleting and creating instances work on RocksDB-backed files file.remove() on a RocksDB-backed file segfaulted. Reducing it turned up five gaps that each made editing such a file crash or silently do nothing: - process_deletion_inverse() decoded the v| inverse-record values as size_t while the serializer, register_inverse(), unregister_inverse() and instances_by_reference() use uint32_t, so std::find failed and vals.erase(end()) was undefined behaviour. It also took the DeleteRange end from an iterator that is invalid when the instance has no inverse records. Decode as uint32_t, remove every occurrence guarded on "found", and derive the range end from the prefix itself. - attribute_value::size() ignored storage_model_, so every aggregate assignment on a RocksDB instance threw "Invalid variant index" from set_attribute_value(). Branch on the storage model like the sibling accessors and count the deserialized aggregate. - rocks_db_file_storage::create() was a stub returning an empty handle, which anything creating an instance then dereferenced. Implement it after in_memory_file_storage::create(). - max_id_ is only initialised by the in-memory parse, so a RocksDB file would have handed out ids that overwrite existing instances. Implement the recalculate_id_counter() stub per backend and run it once before the first fresh_id() on RocksDB. - byid_.erase() was a no-op: set_to_map_transformer::erase() and rocksdb_set_view::erase() were stubs. The deleted instance's attribute keys and cached handle survived, entity_names() still listed it and reopening the database threw. Erase deletes every key under the instance's prefix; the transformer forwards to it and takes an on-erase hook the storage uses to drop the cached handle. root.remove_product on the first 200 products of a 61 MB model now leaves the same surviving ids and inverse counts whether the file was opened from SPF or converted to RocksDB. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: map argument_type to its stored type, size() as size_t Review: argument_type enumerates the members of type_variant_parameter_pack in order, so express that once as argument_storage_type_t<A> (pinned by static_asserts) and let attribute_value::size() on RocksDB go through a single aggregate_size_<A>() helper instead of spelling each vector type out in the switch. size() now returns size_t; its only caller already took size_t. Also build the RocksDB DeleteRange upper bounds as prefix + ('|' + 1) rather than a literal '}', which read as the {id} placeholder notation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: fixed-width hex ids in RocksDB keys, batched instance erasure Review: recalculate_id_counter() scanned every i|<id>|_ record because ids were written as decimal text, which doesn't sort numerically. Make every numeric key segment fixed-width 16-digit lowercase hex, produced and parsed by key_to_string()/key_from_string() in rocksdb_map_adapter.h, with named builders (rocksdb_key::attribute, header_attribute, type_record, inverse, inverse_prefix, type_list, upper_bound) that the storage, entity_instance_data.cpp, read_schema() and the serializer use instead of assembling "i|" + std::to_string(id) + ... by hand. Keys now sort by id, an instance's records are contiguous in id order, and the largest id is the last key under i|, which recalculate_id_counter() seeks to. The two dormant to_string_fixed_width() helpers are gone. This changes the on-disk layout; databases converted before this commit have to be re-converted. Also review: instance_cache_ eviction went through an on-erase hook on set_to_map_transformer, a std::function call under a mutex per deleted instance. Drop the hook; file::remove_entity() and unbatch() call file::erase_instances_(ids), which on RocksDB is rocks_db_file_storage::erase_instances(): one WriteBatch of DeleteRanges and one lock for the whole batch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcopenshell-python: test_rocks queries the fixed-width hex keys The test looked up raw keys in the decimal layout ("i|139|5", "t|<identity>|0"); numeric key segments are fixed-width hex now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 17 天前 | |
ifcparse: the parser as a consumer of scan(), next() no longer called The full parse pulled tokens: for_each_instance_header() and the streamer's three-token window found `#name = KEYWORD (`, then load_attributes() and read_direct_aggregate() recursed over next() for the attribute list, the header section had its own terminal reader, and the lazy index alone was a consumer. Now every pass is a consumer of spf_lexer::scan(): - list_consumer reads an attribute list from just past its '(' through the ')' that closes it. The recursion is an explicit stack of frames, one per open parenthesis: a list frame fills the instance's slots (an entity's list, or a typed value such as IFCLABEL('x') read on its owner's behalf), an aggregate frame accumulates a direct_aggregate, a skip frame counts parentheses for a value beyond the schema's attribute count. Inverse records, unresolved references, the attribute count warning, the enumeration and unknown-type messages and their offsets are made as before; dispatch_token_direct() is folded into the value callbacks. - instance_header_consumer is the top level of the DATA section, the three-token window as consumer states: `#name`, `=`, the keyword (looked up once per spelling), then `(` hands the list to a visit callback whose own scan of the list leaves the lexer where this one continues; the `;` after a list is taken if present, which was try_read_semicolon. It serves the parallel chunk (stop at the chunk end), the lazy index (its attribute_consumer as the visit), and the streamer (one instance per read_instance(), so the serial parse, the streaming wrapper and the RocksDB serializer are unchanged in what they see). - header_consumer is the header section's fixed sequence, the three entities read through load(). - has_semicolon(), semicolon_count() and decode_spf_string() are one small consumer each. next() stays for the tests in this commit and goes in the next one. Behaviour differences, all in malformed input: a token that is not `(` after a header entity's name is an error instead of being swallowed; an unknown or non-entity type in the serial parse is logged once per spelling instead of per instance; an invalid token inside a typed value ends the parse with INVALID_SYNTAX instead of being logged and skipped. 18 Catch2 cases (per-instance equality against the pull parser's fixtures, lazy, parallel and paged equality, the error cases) and 2296 pytest cases pass. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
Restructure and rename | 6 个月前 | |
ifcparse: inline the tokenizer's hot helpers and the paged cursor check callgrind on the tokenizer showed SWAR::has_special_char and eq_mask compiled as calls, one per eight bytes; paged_file_impl::size() out of line behind every eof() and remaining(); and the cursor's page-cache check not inlined into peek() because it shared a function with the page fetch. The SWAR helpers are forced inline, size() is defined in the class, and cached_() is split into an inline check and an out-of-line refresh. TXG (58 MB), single thread: tokenizer 196 -> 204 MB/s in memory and 136 -> 193 MB/s through 64 KB pages; strict parse through pages 1.15 -> 0.97 s against 0.93 s in memory; lazy index pass over pages 218 -> 311 MB/s; lazy open 0.52 -> 0.43 s. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
Parse contiguous instance names without copying Expose the reader's cached span and pass complete names directly to from_chars in the shared tokenizer. Retain the existing path for whitespace, page boundaries and invalid spellings, including overflow. Both consumers use the same shortcut. Add tests for one-byte pages, pushed readers, escaped strings, identifier values and invalid-token errors. Targeted MSVC tests pass. Generated with the assistance of an AI coding tool. | 9 天前 | |
Silence obvious compiler warnings Generated with the assistance of an AI coding tool. | 1 个月前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
Normalize whitespaces in the codebase | 1 个月前 | |
Normalize whitespaces in the codebase | 1 个月前 | |
api.h: format consistently for readibility | 23 天前 | |
ifcparse: lazy loading, opt in with file::lazy_loading() / open(lazy=True) A lazy open reads the DATA section once with the tokenizer's index policy and builds what indexes the file: a shell per instance (name and declaration, no attribute array), the complete inverse index with attribute indices, the GlobalId map and the by-type lists. No attribute value is decoded. The first time an instance's attributes are touched, ensure_loaded() seeks the retained paged reader to the instance and runs the same load_attributes() the full parse runs, with inverse registration off, then resolves that instance's references from its own slots. A modified instance is materialised first, so writing works. There is no scanner of its own: the index pass consumes next<index_tokens>() and counts parentheses and commas on the operator tokens; a keyword where an instance should start, or a token the tokenizer rejects, stops the index and the file is parsed in full. The offset of each instance's attribute list is kept in one sorted vector that exists only in lazy mode, so a full parse pays nothing for it. Materialising from several threads at once is not safe. TXG 58 MB / 210_King 147 MB / OKgate22 231 MB, single thread: lazy open 0.61 / 1.73 / 2.86 s against the full parse's 1.05 / 2.69 / 4.99 s, at 141 / 374 / 534 MB against 274 / 654 / 1036 MB; reading one attribute of every instance afterwards costs a further 0.56 / 1.44 / 4.86 s. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
Silence remaining compiler warnings Generated with the assistance of an AI coding tool. | 1 个月前 | |
Enable retargeting of example schema | 3 个月前 | |
Remove _t suffixes from public types Rename header-scope aliases, enums, and helper types while retaining descriptive names where dropping the suffix would create a collision. Generated with the assistance of an AI coding tool. | 1 个月前 | |
ifcparse: store the GlobalId index in a hash map with inline keys byguid_ was a std::map<std::string, ...>: a red-black node plus a heap-allocated 22-character string per rooted instance, and a lookup that walks ~18 levels of string comparisons on a 200k-entry file. guid_map keeps keys of up to 23 characters inline in an unordered_map node (every valid GlobalId is 22), and routes anything longer to an ordered map so invalid files still work. Same std::string-keyed interface as before. Parse, C++ file constructor, on top of the previous commits: TXG 58 MB 1.08 s -> 1.03 s 341 -> 335 MB 210_King 148 MB 2.80 s -> 2.72 s 836 -> 830 MB OKgate22 232 MB 4.25 s -> 3.94 s 1271 -> 1253 MB This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
[AI-generated, unverified] ifcparse: stop scanning a type's instance list when deleting an instance (#9520) * ifcparse: keep each type's instance list sorted by id remove_type_ref() ran std::remove over every instance of the deleted instance's type, comparing express::base handles (a data() dereference each): O(instances of the type) per deletion, and the bulk of what file.remove() still cost after the inverse index fixes. Keep the per-type lists sorted by id instead. Loading fills them in file order and sort_type_lists() establishes the order once: a list already in id order, the common case, is only checked; otherwise the ids are read once and (id, position) pairs are sorted, the lists in parallel, so a file that isn't in id order costs a few tens of milliseconds to open rather than a sort that dereferences every comparison. add_type_ref() appends a fresh id or inserts an explicit one in place; remove_type_ref() finds the instance by binary search and erases it, a memmove of handles with no dereferences. Nothing about the lists is hidden from their readers, and by_type() now returns instances in id order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: sort the type lists on the loading thread A library has no business sizing a thread pool by hardware_concurrency() on its own; how many threads a file open may use is a caller's decision. Until that is configurable the lists are sorted serially. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 9 天前 | |
ifcparse: remove next() and the token object, the scan body next to the lexer With every pass a consumer of scan(), next(), its one-token consumer and the token object it built have no caller left. The token struct goes from storage.h with its accessors, the two token policies go, and the tokenizer body moves from spf_scan.h into parse.h under the spf_lexer declaration, with a local enum for the kind of body being collected where it used the token type's constants. The tests that used next() as the reference scan with a consumer that decodes everything instead, and the test of the token object's to_string() goes with the object. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 | |
[AI-generated, unverified] ifcparse: make deleting and creating instances work on RocksDB-backed files (#9508) * ifcparse: make deleting and creating instances work on RocksDB-backed files file.remove() on a RocksDB-backed file segfaulted. Reducing it turned up five gaps that each made editing such a file crash or silently do nothing: - process_deletion_inverse() decoded the v| inverse-record values as size_t while the serializer, register_inverse(), unregister_inverse() and instances_by_reference() use uint32_t, so std::find failed and vals.erase(end()) was undefined behaviour. It also took the DeleteRange end from an iterator that is invalid when the instance has no inverse records. Decode as uint32_t, remove every occurrence guarded on "found", and derive the range end from the prefix itself. - attribute_value::size() ignored storage_model_, so every aggregate assignment on a RocksDB instance threw "Invalid variant index" from set_attribute_value(). Branch on the storage model like the sibling accessors and count the deserialized aggregate. - rocks_db_file_storage::create() was a stub returning an empty handle, which anything creating an instance then dereferenced. Implement it after in_memory_file_storage::create(). - max_id_ is only initialised by the in-memory parse, so a RocksDB file would have handed out ids that overwrite existing instances. Implement the recalculate_id_counter() stub per backend and run it once before the first fresh_id() on RocksDB. - byid_.erase() was a no-op: set_to_map_transformer::erase() and rocksdb_set_view::erase() were stubs. The deleted instance's attribute keys and cached handle survived, entity_names() still listed it and reopening the database threw. Erase deletes every key under the instance's prefix; the transformer forwards to it and takes an on-erase hook the storage uses to drop the cached handle. root.remove_product on the first 200 products of a 61 MB model now leaves the same surviving ids and inverse counts whether the file was opened from SPF or converted to RocksDB. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: map argument_type to its stored type, size() as size_t Review: argument_type enumerates the members of type_variant_parameter_pack in order, so express that once as argument_storage_type_t<A> (pinned by static_asserts) and let attribute_value::size() on RocksDB go through a single aggregate_size_<A>() helper instead of spelling each vector type out in the switch. size() now returns size_t; its only caller already took size_t. Also build the RocksDB DeleteRange upper bounds as prefix + ('|' + 1) rather than a literal '}', which read as the {id} placeholder notation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: fixed-width hex ids in RocksDB keys, batched instance erasure Review: recalculate_id_counter() scanned every i|<id>|_ record because ids were written as decimal text, which doesn't sort numerically. Make every numeric key segment fixed-width 16-digit lowercase hex, produced and parsed by key_to_string()/key_from_string() in rocksdb_map_adapter.h, with named builders (rocksdb_key::attribute, header_attribute, type_record, inverse, inverse_prefix, type_list, upper_bound) that the storage, entity_instance_data.cpp, read_schema() and the serializer use instead of assembling "i|" + std::to_string(id) + ... by hand. Keys now sort by id, an instance's records are contiguous in id order, and the largest id is the last key under i|, which recalculate_id_counter() seeks to. The two dormant to_string_fixed_width() helpers are gone. This changes the on-disk layout; databases converted before this commit have to be re-converted. Also review: instance_cache_ eviction went through an on-erase hook on set_to_map_transformer, a std::function call under a mutex per deleted instance. Drop the hook; file::remove_entity() and unbatch() call file::erase_instances_(ids), which on RocksDB is rocks_db_file_storage::erase_instances(): one WriteBatch of DeleteRanges and one lock for the whole batch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcopenshell-python: test_rocks queries the fixed-width hex keys The test looked up raw keys in the decimal layout ("i|139|5", "t|<identity>|0"); numeric key segments are fixed-width hex now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 17 天前 | |
[AI-generated, unverified] ifcparse: make deleting and creating instances work on RocksDB-backed files (#9508) * ifcparse: make deleting and creating instances work on RocksDB-backed files file.remove() on a RocksDB-backed file segfaulted. Reducing it turned up five gaps that each made editing such a file crash or silently do nothing: - process_deletion_inverse() decoded the v| inverse-record values as size_t while the serializer, register_inverse(), unregister_inverse() and instances_by_reference() use uint32_t, so std::find failed and vals.erase(end()) was undefined behaviour. It also took the DeleteRange end from an iterator that is invalid when the instance has no inverse records. Decode as uint32_t, remove every occurrence guarded on "found", and derive the range end from the prefix itself. - attribute_value::size() ignored storage_model_, so every aggregate assignment on a RocksDB instance threw "Invalid variant index" from set_attribute_value(). Branch on the storage model like the sibling accessors and count the deserialized aggregate. - rocks_db_file_storage::create() was a stub returning an empty handle, which anything creating an instance then dereferenced. Implement it after in_memory_file_storage::create(). - max_id_ is only initialised by the in-memory parse, so a RocksDB file would have handed out ids that overwrite existing instances. Implement the recalculate_id_counter() stub per backend and run it once before the first fresh_id() on RocksDB. - byid_.erase() was a no-op: set_to_map_transformer::erase() and rocksdb_set_view::erase() were stubs. The deleted instance's attribute keys and cached handle survived, entity_names() still listed it and reopening the database threw. Erase deletes every key under the instance's prefix; the transformer forwards to it and takes an on-erase hook the storage uses to drop the cached handle. root.remove_product on the first 200 products of a 61 MB model now leaves the same surviving ids and inverse counts whether the file was opened from SPF or converted to RocksDB. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: map argument_type to its stored type, size() as size_t Review: argument_type enumerates the members of type_variant_parameter_pack in order, so express that once as argument_storage_type_t<A> (pinned by static_asserts) and let attribute_value::size() on RocksDB go through a single aggregate_size_<A>() helper instead of spelling each vector type out in the switch. size() now returns size_t; its only caller already took size_t. Also build the RocksDB DeleteRange upper bounds as prefix + ('|' + 1) rather than a literal '}', which read as the {id} placeholder notation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: fixed-width hex ids in RocksDB keys, batched instance erasure Review: recalculate_id_counter() scanned every i|<id>|_ record because ids were written as decimal text, which doesn't sort numerically. Make every numeric key segment fixed-width 16-digit lowercase hex, produced and parsed by key_to_string()/key_from_string() in rocksdb_map_adapter.h, with named builders (rocksdb_key::attribute, header_attribute, type_record, inverse, inverse_prefix, type_list, upper_bound) that the storage, entity_instance_data.cpp, read_schema() and the serializer use instead of assembling "i|" + std::to_string(id) + ... by hand. Keys now sort by id, an instance's records are contiguous in id order, and the largest id is the last key under i|, which recalculate_id_counter() seeks to. The two dormant to_string_fixed_width() helpers are gone. This changes the on-disk layout; databases converted before this commit have to be re-converted. Also review: instance_cache_ eviction went through an on-erase hook on set_to_map_transformer, a std::function call under a mutex per deleted instance. Drop the hook; file::remove_entity() and unbatch() call file::erase_instances_(ids), which on RocksDB is rocks_db_file_storage::erase_instances(): one WriteBatch of DeleteRanges and one lock for the whole batch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcopenshell-python: test_rocks queries the fixed-width hex keys The test looked up raw keys in the decimal layout ("i|139|5", "t|<identity>|0"); numeric key segments are fixed-width hex now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 17 天前 | |
Use underscore plugin artifact names Generated with the assistance of an AI coding tool. | 1 个月前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
Silence remaining compiler warnings Generated with the assistance of an AI coding tool. | 1 个月前 | |
[AI-generated, unverified] ifcparse: make deleting and creating instances work on RocksDB-backed files (#9508) * ifcparse: make deleting and creating instances work on RocksDB-backed files file.remove() on a RocksDB-backed file segfaulted. Reducing it turned up five gaps that each made editing such a file crash or silently do nothing: - process_deletion_inverse() decoded the v| inverse-record values as size_t while the serializer, register_inverse(), unregister_inverse() and instances_by_reference() use uint32_t, so std::find failed and vals.erase(end()) was undefined behaviour. It also took the DeleteRange end from an iterator that is invalid when the instance has no inverse records. Decode as uint32_t, remove every occurrence guarded on "found", and derive the range end from the prefix itself. - attribute_value::size() ignored storage_model_, so every aggregate assignment on a RocksDB instance threw "Invalid variant index" from set_attribute_value(). Branch on the storage model like the sibling accessors and count the deserialized aggregate. - rocks_db_file_storage::create() was a stub returning an empty handle, which anything creating an instance then dereferenced. Implement it after in_memory_file_storage::create(). - max_id_ is only initialised by the in-memory parse, so a RocksDB file would have handed out ids that overwrite existing instances. Implement the recalculate_id_counter() stub per backend and run it once before the first fresh_id() on RocksDB. - byid_.erase() was a no-op: set_to_map_transformer::erase() and rocksdb_set_view::erase() were stubs. The deleted instance's attribute keys and cached handle survived, entity_names() still listed it and reopening the database threw. Erase deletes every key under the instance's prefix; the transformer forwards to it and takes an on-erase hook the storage uses to drop the cached handle. root.remove_product on the first 200 products of a 61 MB model now leaves the same surviving ids and inverse counts whether the file was opened from SPF or converted to RocksDB. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: map argument_type to its stored type, size() as size_t Review: argument_type enumerates the members of type_variant_parameter_pack in order, so express that once as argument_storage_type_t<A> (pinned by static_asserts) and let attribute_value::size() on RocksDB go through a single aggregate_size_<A>() helper instead of spelling each vector type out in the switch. size() now returns size_t; its only caller already took size_t. Also build the RocksDB DeleteRange upper bounds as prefix + ('|' + 1) rather than a literal '}', which read as the {id} placeholder notation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: fixed-width hex ids in RocksDB keys, batched instance erasure Review: recalculate_id_counter() scanned every i|<id>|_ record because ids were written as decimal text, which doesn't sort numerically. Make every numeric key segment fixed-width 16-digit lowercase hex, produced and parsed by key_to_string()/key_from_string() in rocksdb_map_adapter.h, with named builders (rocksdb_key::attribute, header_attribute, type_record, inverse, inverse_prefix, type_list, upper_bound) that the storage, entity_instance_data.cpp, read_schema() and the serializer use instead of assembling "i|" + std::to_string(id) + ... by hand. Keys now sort by id, an instance's records are contiguous in id order, and the largest id is the last key under i|, which recalculate_id_counter() seeks to. The two dormant to_string_fixed_width() helpers are gone. This changes the on-disk layout; databases converted before this commit have to be re-converted. Also review: instance_cache_ eviction went through an on-erase hook on set_to_map_transformer, a std::function call under a mutex per deleted instance. Drop the hook; file::remove_entity() and unbatch() call file::erase_instances_(ids), which on RocksDB is rocks_db_file_storage::erase_instances(): one WriteBatch of DeleteRanges and one lock for the whole batch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcopenshell-python: test_rocks queries the fixed-width hex keys The test looked up raw keys in the decimal layout ("i|139|5", "t|<identity>|0"); numeric key segments are fixed-width hex now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 17 天前 | |
Continue work on plug-in and tests | 4 个月前 | |
Silence obvious compiler warnings Generated with the assistance of an AI coding tool. | 1 个月前 | |
Remove _t suffixes from public types Rename header-scope aliases, enums, and helper types while retaining descriptive names where dropping the suffix would create a collision. Generated with the assistance of an AI coding tool. | 1 个月前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
[AI-generated, unverified] ifcparse: stop scanning a type's instance list when deleting an instance (#9520) * ifcparse: keep each type's instance list sorted by id remove_type_ref() ran std::remove over every instance of the deleted instance's type, comparing express::base handles (a data() dereference each): O(instances of the type) per deletion, and the bulk of what file.remove() still cost after the inverse index fixes. Keep the per-type lists sorted by id instead. Loading fills them in file order and sort_type_lists() establishes the order once: a list already in id order, the common case, is only checked; otherwise the ids are read once and (id, position) pairs are sorted, the lists in parallel, so a file that isn't in id order costs a few tens of milliseconds to open rather than a sort that dereferences every comparison. add_type_ref() appends a fresh id or inserts an explicit one in place; remove_type_ref() finds the instance by binary search and erases it, a memmove of handles with no dereferences. Nothing about the lists is hidden from their readers, and by_type() now returns instances in id order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH * ifcparse: sort the type lists on the loading thread A library has no business sizing a thread pool by hardware_concurrency() on its own; how many threads a file open may use is a caller's decision. Until that is configurable the lists are sorted serially. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HNrXDmR88wKPCYwGE21SyH --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 9 天前 | |
Wrap more classes into ifcopenshell:: namespace | 1 个月前 | |
Merge remote-tracking branch 'origin/v0.8.0' into ifcviewer-wgpu | 2 个月前 | |
ifcparse: two heap allocations per instance instead of four Each instance was four separate allocations: the instance_data record, the attribute array object it pointed at, that array's index bytes, and its slot storage. A 58 MB model made 10.4 million mallocs to load 918k instances, and massif attributed 99 MB of its 450 MB peak to malloc bookkeeping alone. variant_array now allocates the size byte, the per-slot type indices and the slots as one block, and instance_data holds the array in a std::optional instead of behind a pointer (an empty optional keeps the meaning the null pointer had: attribute storage constructed on the fly from the RocksDB backend). No ownership or lifetime changes; the same object owns the same data. Parse, C++ file constructor, on top of the previous commits: TXG 58 MB 0.96 -> 0.94 s steady 310 -> 262 MB peak 383 -> 335 MB 210_King 148 MB 2.74 -> 2.63 s steady 758 -> 630 MB peak 948 -> 820 MB OKgate22 232 MB 3.70 -> 3.61 s steady 1165 -> 971 MB peak 1447 -> 1252 MB mallocs while loading TXG: 10.39M -> 7.56M. This commit was written by an AI coding tool and has not been verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL | 9 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 9 天前 | ||
| 14 天前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 14 天前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 6 个月前 | ||
| 6 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 17 天前 | ||
| 9 天前 | ||
| 6 个月前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 23 天前 | ||
| 9 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 3 个月前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 9 天前 | ||
| 17 天前 | ||
| 17 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 17 天前 | ||
| 4 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 9 天前 | ||
| 1 个月前 | ||
| 2 个月前 | ||
| 9 天前 |