Skip to content

Fb global + unit tests#19

Merged
artemijan merged 8 commits intomasterfrom
fb-global-id
Nov 27, 2025
Merged

Fb global + unit tests#19
artemijan merged 8 commits intomasterfrom
fb-global-id

Conversation

@artemijan
Copy link
Copy Markdown
Owner

@artemijan artemijan commented Nov 25, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Fixed quest state validation logic for proper state checking.
  • Refactor

    • Introduced a centralized ID factory and explicit object identifiers across entities; packet assemblies updated to use the new object identifier uniformly.
  • Tests

    • Updated tests for deterministic object IDs and adjusted expected packet payloads to match the new identifier handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Nov 25, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a thread-safe IdFactory and ObjectId wrapper, introduces object_id fields for Player and ItemObject, migrates many packet serializers and tests to use get_object_id(), changes item storage iteration to HashMap, and adjusts several player/party/quest method signatures and usages.

Changes

Cohort / File(s) Change Summary
ID Factory
l2-core/src/id_factory.rs, l2-core/src/lib.rs
New public id_factory module: IdFactory singleton with Atomic/Mutex-backed reusable ID pool and ObjectId (Arc-wrapped i32). Implements allocation, reuse, Drop-based release, conversion and comparison impls; exported from crate root.
Core object identities
l2-core/src/game_objects/player/_player.rs, l2-core/src/game_objects/item/_item.rs
Added public object_id: ObjectId to Player and ItemObject. Players and items are assigned ObjectIds from IdFactory; Player exposes get_object_id() (replaces prior get_id/char_model.id usage). ItemObject mapping now keyed by object_id.
Player internals & APIs
l2-core/src/game_objects/player/paper_doll.rs, l2-core/src/game_objects/player/party.rs, l2-core/src/game_objects/player/quest.rs
PaperDoll::restore_visible_inventory now accepts HashMap<i32, ItemObject> and iterates values; index_of renamed to player_object_id and uses get_object_id(); Quest::has_state renamed param and simplified logic.
Packet serialization updates
game/src/packets/from_client/restart.rs, game/src/packets/to_client/char_info.rs, game/src/packets/to_client/char_move_to_location.rs, game/src/packets/to_client/char_selected.rs, game/src/packets/to_client/extended/equipped_items.rs, game/src/packets/to_client/extended/inventory_weight.rs, game/src/packets/to_client/extended/premium_state.rs, game/src/packets/to_client/extended/rotation.rs, game/src/packets/to_client/move_to.rs, game/src/packets/to_client/quest_list.rs, game/src/packets/to_client/relation_changed.rs, game/src/packets/to_client/skill_list.rs, game/src/packets/to_client/user_info.rs
Replaced usages of char_model.id / get_id() with get_object_id() throughout packet builders and relation logic; updated tests to set player.object_id via ObjectId::new(...) and adjusted expected serialized bytes. Also updated DeleteObject::new call to use player.get_object_id().
Tests & small cleanups
game/* tests (various), login/src/packet/handleable/player_logout.rs, login/src/enums.rs, l2-core/src/data/char_template.rs, l2-core/Cargo.toml
Test adjustments to construct/set ObjectId, updated expected buffers. Minor code cleanups: removed ntest.workspace flag in dev-deps, collapsed multi-line let bindings, added tracing imports, and simplified single-element slice creation in a test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus areas:
    • l2-core/src/id_factory.rs (thread-safety, Drop semantics, reuse correctness)
    • Player and ItemObject initialization order in l2-core/src/game_objects/player/_player.rs and item/_item.rs
    • PaperDoll::restore_visible_inventory signature and matrix population logic (indexing/VisualId parsing)
    • Packet serializers and tests to ensure correct id values and no off-by-one/endianness issues

Possibly related PRs

  • Fb global + unit tests #19 — Implements the same migration from char_model.id/get_id to ObjectId/get_object_id across packets and restart DeleteObject call.
  • Implement basic move to location #14 — Earlier changes to MoveToLocation/CharMoveToLocation and Player id access that this PR continues to migrate to the ObjectId system.

Poem

🐇 I found fresh ids in a tidy queue,

Arc-wrapped numbers, reused and true.
Packets now whisper the new object name,
Players and items no longer the same.
Hooray — I hop, and stamp my new id too!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.21% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Fb global + unit tests' is vague and does not clearly describe the main changes. The PR implements a comprehensive refactoring introducing ObjectId, IdFactory, and centralizing identity management across the codebase, but the title obscures this with abbreviated, non-descriptive phrasing. Replace with a descriptive title like 'Introduce ObjectId and centralize identity management' or 'Refactor to use global object identity factory' to clarify the main purpose of these changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8366a and 7e861cf.

📒 Files selected for processing (1)
  • l2-core/src/id_factory.rs (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
login/src/enums.rs (1)

3-23: Prefer tracing logging over println! in state transitions

Since tracing::{error, info} is now imported, consider logging the illegal transition via error! instead of println! to keep diagnostics consistent:

 use l2_core::shared_packets::common::GSLoginFailReasons;
 use strum::Display;
-use tracing::{error, info};
+use tracing::{error, info};
@@
         match (&self, desired_state) {
@@
-            (Self::BfConnected, Self::Authed) => *self = Self::Authed,
-            _ => {
-                println!("Can't transition from connection state {self} to {desired_state}");
-                return Err(GSLoginFailReasons::NotAuthed);
-            }
+            (Self::BfConnected, Self::Authed) => *self = Self::Authed,
+            _ => {
+                error!(
+                    "Can't transition from connection state {:?} to {:?}",
+                    self, desired_state
+                );
+                return Err(GSLoginFailReasons::NotAuthed);
+            }
         }

Please ensure tracing is properly initialized in the login binary so these logs are actually emitted.

game/src/packets/to_client/relation_changed.rs (1)

45-53: Use of get_object_id() is correct; consider avoiding name shadowing

Switching obj_id to player.get_object_id() aligns this packet with the new object‑ID mechanism and looks correct.

As a small readability tweak, the local let relation = Relation { ... } currently shadows the relation: u32 parameter. Consider renaming the local (e.g., rel_entry or relation_entry) to avoid shadowing.

l2-core/src/game_objects/item/_item.rs (1)

9-9: Unused import: Arc

The Arc import was added but doesn't appear to be used in the changed code within this file. If it's not needed elsewhere in the file (the full context shows no usage), consider removing it.

-use std::sync::Arc;
l2-core/src/game_objects/player/paper_doll.rs (1)

97-97: Consider using the imported HashMap type alias.

The function signature uses the fully qualified std::collections::HashMap path. Since HashMap is a common type, consider adding use std::collections::HashMap; at the top of the file for cleaner signatures.

 use entities::dao::item::{ItemVariables, LocType};
 use entities::entities::item;
+use std::collections::HashMap;

Then update the signature:

-    pub fn restore_visible_inventory(items: &std::collections::HashMap<i32, crate::game_objects::item::ItemObject>) -> [[i32; 4]; 33] {
+    pub fn restore_visible_inventory(items: &HashMap<i32, crate::game_objects::item::ItemObject>) -> [[i32; 4]; 33] {
l2-core/src/game_objects/player/_player.rs (1)

806-808: Unnecessary separate impl block.

This creates a second impl Player block but contains no new constraints or generics. Consider merging with the existing impl Player block above (lines 73-806) for better code organization.

l2-core/src/id_factory.rs (1)

102-143: Tests share global singleton state without reset.

Since IdFactory::instance() returns a global singleton, test execution order affects outcomes. The #[serial_test::serial] attribute prevents parallel execution, but accumulated state from previous tests can cause failures when tests run in different orders or in CI.

Consider adding a test-only reset method or using a test-scoped factory instance.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2976e66 and 5ed40ee.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • Cargo.toml (1 hunks)
  • game/src/packets/from_client/restart.rs (1 hunks)
  • game/src/packets/to_client/char_info.rs (1 hunks)
  • game/src/packets/to_client/char_move_to_location.rs (1 hunks)
  • game/src/packets/to_client/char_selected.rs (3 hunks)
  • game/src/packets/to_client/extended/equipped_items.rs (3 hunks)
  • game/src/packets/to_client/extended/inventory_weight.rs (3 hunks)
  • game/src/packets/to_client/extended/premium_state.rs (3 hunks)
  • game/src/packets/to_client/extended/rotation.rs (3 hunks)
  • game/src/packets/to_client/move_to.rs (1 hunks)
  • game/src/packets/to_client/quest_list.rs (2 hunks)
  • game/src/packets/to_client/relation_changed.rs (1 hunks)
  • game/src/packets/to_client/skill_list.rs (0 hunks)
  • game/src/packets/to_client/user_info.rs (5 hunks)
  • l2-core/Cargo.toml (1 hunks)
  • l2-core/src/data/char_template.rs (1 hunks)
  • l2-core/src/game_objects/item/_item.rs (3 hunks)
  • l2-core/src/game_objects/player/_player.rs (8 hunks)
  • l2-core/src/game_objects/player/paper_doll.rs (1 hunks)
  • l2-core/src/game_objects/player/party.rs (1 hunks)
  • l2-core/src/game_objects/player/quest.rs (1 hunks)
  • l2-core/src/id_factory.rs (1 hunks)
  • l2-core/src/lib.rs (1 hunks)
  • login/src/enums.rs (1 hunks)
  • login/src/packet/handleable/player_logout.rs (1 hunks)
💤 Files with no reviewable changes (1)
  • game/src/packets/to_client/skill_list.rs
🧰 Additional context used
🧬 Code graph analysis (10)
game/src/packets/from_client/restart.rs (2)
game/src/packets/to_client/char_selected.rs (1)
  • new (16-56)
game/src/packets/to_client/delete_object.rs (1)
  • new (13-21)
game/src/packets/to_client/extended/rotation.rs (2)
l2-core/src/id_factory.rs (1)
  • new (18-20)
l2-core/src/game_objects/player/_player.rs (1)
  • new (77-116)
game/src/packets/to_client/quest_list.rs (1)
l2-core/src/id_factory.rs (1)
  • new (18-20)
game/src/packets/to_client/extended/inventory_weight.rs (2)
game/src/packets/to_client/extended/premium_state.rs (1)
  • new (14-21)
l2-core/src/game_objects/player/_player.rs (1)
  • new (77-116)
l2-core/src/id_factory.rs (1)
l2-core/src/game_objects/player/_player.rs (3)
  • new (77-116)
  • from (37-43)
  • from (46-48)
l2-core/src/game_objects/item/_item.rs (1)
l2-core/src/id_factory.rs (1)
  • instance (63-73)
game/src/packets/to_client/user_info.rs (1)
l2-core/src/game_objects/player/_player.rs (1)
  • new (77-116)
game/src/packets/to_client/extended/equipped_items.rs (4)
l2-core/src/id_factory.rs (1)
  • new (18-20)
game/src/packets/to_client/extended/premium_state.rs (1)
  • new (14-21)
game/src/packets/to_client/extended/rotation.rs (1)
  • new (15-24)
game/src/packets/to_client/char_info.rs (1)
  • new (47-198)
login/src/packet/handleable/player_logout.rs (1)
login/src/controller/player_management.rs (2)
  • on_players_in_game (94-112)
  • i (66-66)
game/src/packets/to_client/char_selected.rs (3)
l2-core/src/id_factory.rs (1)
  • new (18-20)
l2-core/src/game_objects/player/_player.rs (1)
  • new (77-116)
game/src/packets/to_client/char_selection.rs (1)
  • new (21-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test-only
  • GitHub Check: test-only
🔇 Additional comments (30)
l2-core/src/data/char_template.rs (1)

142-144: Base max HP/MP/CP bindings refactor is safe

Inlining the three get_base_max_parameter calls into single-line let bindings preserves behavior and keeps error handling unchanged; looks good.

game/src/packets/to_client/char_info.rs (1)

58-58: Using get_object_id() in CharInfo matches the new ID model

Writing the object ID via p.get_object_id() (rather than reaching into char_model.id) is consistent with the new ID factory/ObjectId flow and matches how other packets identify the in‑world object.

l2-core/src/lib.rs (1)

25-25: Exposing id_factory at the crate root is appropriate

Making id_factory a public module here is a clean way to surface the new Id/IdFactory functionality to the rest of the codebase.

game/src/packets/to_client/move_to.rs (1)

17-17: MoveTo now using get_object_id() is consistent with the new ID scheme

Using p.get_object_id() for the moving object's identifier keeps this packet in line with the centralized ObjectId/IdFactory approach; no issues from this change.

Cargo.toml (1)

63-63: Workspace serial_test dependency addition is valid and actively used

Verification confirms serial_test = "3.2.0" is properly declared and actively used in l2-core/src/id_factory.rs (3 test functions with #[serial_test::serial] attributes). Version 3.2.0 is legitimate, MIT-licensed, and published on crates.io with MSRV 1.68.2.

game/src/packets/to_client/quest_list.rs (1)

51-72: Deterministic ObjectId usage in test looks good

Importing ObjectId and explicitly setting player.object_id makes the test independent of the global IdFactory state and fits the new object‑ID model. No issues from a correctness standpoint.

login/src/packet/handleable/player_logout.rs (1)

45-52: Using std::slice::from_ref is correct and efficient

on_players_in_game expects &[String], and std::slice::from_ref(&acc) satisfies that without allocating a new Vec. Because the controller clones acc_name internally, there are no lifetime or ownership issues.

game/src/packets/to_client/char_move_to_location.rs (1)

16-31: CharMoveToLocation now correctly uses object_id

Writing p.get_object_id() into the packet keeps this serializer consistent with the rest of the protocol, which now keys entities by object ID rather than the character model ID. The rest of the payload remains unchanged and correct.

game/src/packets/to_client/extended/rotation.rs (1)

15-24: Rotation packet correctly migrated to object_id with matching test

Rotation::new now writes player.get_object_id(), and the test fixes player.object_id via ObjectId::new(268_476_210) and adjusts the expected bytes accordingly. The asserted buffer slice encodes both the object ID and heading in little‑endian as expected, so the test gives good coverage of the new behavior.

Also applies to: 30-64

game/src/packets/to_client/extended/premium_state.rs (1)

14-21: PremiumState now uses object_id consistently; test is in sync

Switching to p.get_object_id() for the identifier, and explicitly setting player.object_id in the test, brings this packet in line with the new ID model. The updated expected payload matches the configured object ID, so the test correctly validates the new serialization.

Also applies to: 33-34, 55-60

game/src/packets/to_client/extended/inventory_weight.rs (1)

14-24: InventoryWeight packet correctly switched to object_id and test cleaned up

Using p.get_object_id() for the identifier and assigning player.object_id in the test aligns this packet with the new object‑ID system. The updated expected bytes correctly encode the packet and object ID, and dropping mut from char removes an unnecessary mutability marker.

Also applies to: 36-37, 42-47, 56-61

game/src/packets/to_client/char_selected.rs (1)

16-26: CharSelected now serializes the correct object_id

Writing player.get_object_id() instead of the character model ID ensures CharSelected carries the runtime object identifier used elsewhere in the protocol. The test’s explicit ObjectId::new(268_476_207) and adjusted expected bytes (47, 159, 0, 16) correctly validate this behavior.

Also applies to: 64-65, 89-91, 94-104

game/src/packets/from_client/restart.rs (1)

34-39: Restart flow now deletes the correct object_id

Passing player.get_object_id() into DeleteObject::new makes the restart broadcast target the same object ID the client uses for that character, instead of the model/DB ID. This is consistent with the rest of the refactor and should prevent “ghost” objects on other clients.

l2-core/src/game_objects/item/_item.rs (1)

43-65: LGTM! Clean integration of ObjectId into ItemObject.

The object_id field addition and the updated from_items logic correctly generate unique IDs via IdFactory::instance().get_next_id() for each item. The HashMap key now uses the object_id value, which aligns with the broader migration to object-based identifiers.

l2-core/src/game_objects/player/quest.rs (1)

14-19: LGTM! Clean refactor using is_some_and().

The simplified logic using .is_some_and(|state| state.eq(expected_state)) is more idiomatic and eliminates the need for separate boolean handling. The behavior remains equivalent.

l2-core/src/game_objects/player/party.rs (1)

44-61: LGTM! Consistent migration to object_id-based identity.

Both get_leader_id() and index_of() now correctly use get_object_id() for player identity comparisons, aligning with the codebase-wide transition from char_model.id to the new object ID system. The PartialEq implementation (lines 13-17) that relies on get_leader_id() remains consistent.

game/src/packets/to_client/user_info.rs (4)

55-55: LGTM! Correct migration to get_object_id() for packet serialization.

The packet header now correctly uses player.get_object_id() instead of directly accessing char_model.id, aligning with the new object identity system.


59-67: Verify: Mixed usage of char_model.id and get_object_id().

Line 64 still uses player.char_model.id for the is_clan_leader check while other identity comparisons now use get_object_id(). This may be intentional if is_clan_leader requires the persistent database ID for clan membership lookups, but please verify this is the intended behavior and not an oversight in the migration.


426-431: LGTM! Consistent party leader comparison.

The relation logic correctly compares pt.get_leader_id() with p.get_object_id() for determining party leadership, consistent with the changes in party.rs.


495-520: LGTM! Test correctly updated for ObjectId.

The test now properly initializes player.object_id with a specific ObjectId value and the expected byte array is updated to match ([44, 159, 0, 16] = 268_476_204 in little-endian i32).

game/src/packets/to_client/extended/equipped_items.rs (2)

24-24: LGTM! Consistent with other packet serialization changes.

The packet correctly uses p.get_object_id() for writing the player's identity, matching the patterns in premium_state.rs, rotation.rs, and char_info.rs.


85-90: LGTM! Test correctly updated for ObjectId.

The test properly sets player.object_id = ObjectId::new(268_476_206) and the expected byte array reflects this value correctly ([46, 159, 0, 16] = little-endian representation of 268_476_206).

l2-core/src/game_objects/player/paper_doll.rs (1)

99-117: LGTM! Correctly adapted for the new ItemObject structure.

The iteration and field access patterns are correctly updated:

  • items.values() iterates over the HashMap's ItemObject values
  • item.item_model.loc and item.item_model.loc_data access the nested model
  • item.object_id.value() correctly retrieves the i32 ID for the result array

The visual ID extraction and override logic at lines 105-115 is preserved correctly.

l2-core/src/game_objects/player/_player.rs (5)

20-20: LGTM!

Import addition for IdFactory and ObjectId aligns with the centralized ID management system.


51-53: LGTM!

Adding object_id: ObjectId as a public field on Player is consistent with the new identity model. Since ObjectId derives Clone, Player can still derive Clone.


82-87: LGTM!

The refactored construction flow is well-structured: creating Inventory from items first, then restoring PaperDoll visibility based on inventory items, and finally obtaining a unique object_id from the singleton factory.

Also applies to: 114-114


152-154: Semantic shift from persistent to transient ID.

This changes the identity model from char_model.id (database primary key, persistent) to object_id (runtime-generated, recycled). Ensure all call sites expecting a persistent character ID are updated to use char_model.id directly if persistence is required (e.g., for database operations or cross-session references).


890-892: LGTM!

Using self.get_object_id() for party index lookup is consistent with the new identity model.

l2-core/src/id_factory.rs (2)

48-55: LGTM!

The Drop implementation correctly checks Arc::strong_count(&self.0) == 1 before releasing. Since we're inside drop, we exclusively hold this reference—if the count is 1, no other clones exist, making it safe to release the ID back to the pool.


62-73: LGTM!

The singleton pattern using OnceLock is correct and thread-safe.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
l2-core/src/id_factory.rs (1)

63-83: Allocator logic and concurrency look sound; consider long‑term overflow and contention trade‑offs

The singleton + AtomicI32 + Mutex<HashSet<i32>> combination gives you a straightforward, race‑free allocator: get_next_id now removes from the reuse set while holding the lock, so the TOCTOU race from the earlier version is gone, and fetch_add on next_id guarantees uniqueness across threads.

Two non‑blocking points to consider:

  1. Potential i32 overflow
    If this factory is used on a long‑running server with a very high allocation rate and relatively little reuse, next_id: AtomicI32 starting at FIRST_OID can eventually wrap. That’s very far out in practice, but if you want to be future‑proof you could:

    • Switch to AtomicI64 and i64 IDs, or
    • Add a checked upper bound with a clear panic/log when exhausted.
  2. Global mutex contention vs. DashSet
    Moving from a sharded structure to Mutex<HashSet<_>> simplifies correctness, but also serializes all reuse operations on a single lock. If ID churn ever becomes a hot path, you might eventually want to revisit a lock‑free or sharded design (e.g. DashSet) with atomic “pop” semantics to avoid pool races, instead of putting everything behind one Mutex. For now this is fine, but worth being aware of the trade‑off.

Also applies to: 85-95

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 298a1ec and 3f92c2b.

📒 Files selected for processing (1)
  • l2-core/src/id_factory.rs (1 hunks)

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
l2-core/src/id_factory.rs (1)

1-1: Remove unused DashSet import.

DashSet is imported but never used. The implementation uses Mutex<HashSet<i32>> instead (line 12).

-use dashmap::DashSet;
 use log::warn;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f92c2b and 3b8366a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • l2-core/Cargo.toml (1 hunks)
  • l2-core/src/id_factory.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
l2-core/src/id_factory.rs (1)
l2-core/src/game_objects/player/_player.rs (3)
  • new (77-116)
  • from (37-43)
  • from (46-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test-only
🔇 Additional comments (6)
l2-core/src/id_factory.rs (5)

7-15: LGTM! Well-designed ID management types.

The constant, struct definitions, and trait derivations are appropriate:

  • FIRST_OID provides a clear starting boundary for object IDs
  • AtomicI32 and Mutex<HashSet<i32>> ensure thread-safe ID allocation and reuse
  • ObjectId wrapping Arc<i32> enables shared ownership with automatic release tracking

17-61: LGTM! Comprehensive trait implementations.

The ObjectId implementation and trait impls are well-designed:

  • #[must_use] attributes prevent accidental misuse
  • Bidirectional PartialEq and PartialOrd with i32 enable ergonomic comparisons
  • The Drop implementation correctly checks Arc::strong_count == 1 before releasing, ensuring IDs are only returned to the pool when the last reference is dropped

64-74: LGTM! Thread-safe singleton pattern.

The singleton implementation using OnceLock is correct and provides thread-safe lazy initialization. Returning Arc clones enables safe shared access across the application.


76-90: LGTM! Race condition successfully resolved.

The get_next_id implementation now correctly holds the mutex lock throughout the entire read-remove operation (lines 77-82), eliminating the TOCTOU race condition flagged in previous reviews. The release_id method appropriately warns on double-release attempts.


98-144: LGTM! Test isolation issues successfully resolved.

The tests now implement the reset_for_tests() helper suggested in previous reviews and call it at the start of each test (lines 112, 122, 136). This ensures deterministic behavior by:

  • Clearing the reusable ID pool
  • Resetting the ID counter to FIRST_OID
  • Eliminating dependencies on global state and HashSet iteration order

The test assertions have also been improved to verify specific behaviors (e.g., checking contains() in line 130) rather than relying on ordering guarantees.

l2-core/Cargo.toml (1)

44-45: The serial_test removal is confirmed to be intentional. No usages of serial_test were found anywhere in the l2-core tests, confirming that the migration to using reset_for_tests() is complete and the dependency can be safely removed.

@artemijan artemijan merged commit 20db175 into master Nov 27, 2025
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant