Implement atomic food claiming mechanism to prevent race conditions during consumption
This commit is contained in:
parent
78438ae768
commit
19b946949d
@ -96,11 +96,27 @@ class FoodObject(BaseEntity):
|
||||
self.max_decay = 400
|
||||
self.interaction_radius: int = 50
|
||||
self.neighbors: int = 0
|
||||
self.claimed_this_tick = False # Track if food was claimed this tick
|
||||
self.flags: dict[str, bool] = {
|
||||
"death": False,
|
||||
"can_interact": True,
|
||||
}
|
||||
|
||||
def try_claim(self) -> bool:
|
||||
"""
|
||||
Atomically claims this food for consumption within the current tick.
|
||||
|
||||
:return: True if successfully claimed, False if already claimed this tick
|
||||
"""
|
||||
if not self.claimed_this_tick:
|
||||
self.claimed_this_tick = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def reset_claim(self) -> None:
|
||||
"""Reset the claim for next tick"""
|
||||
self.claimed_this_tick = False
|
||||
|
||||
def tick(self, interactable: Optional[List[BaseEntity]] = None) -> Union["FoodObject", List["FoodObject"]]:
|
||||
"""
|
||||
Updates the food object, increasing decay and flagging for death if decayed.
|
||||
@ -108,6 +124,9 @@ class FoodObject(BaseEntity):
|
||||
:param interactable: List of nearby entities (unused).
|
||||
:return: Self
|
||||
"""
|
||||
# Reset claim at the beginning of each tick
|
||||
self.reset_claim()
|
||||
|
||||
if interactable is None:
|
||||
interactable = []
|
||||
|
||||
@ -300,8 +319,10 @@ class DefaultCell(BaseEntity):
|
||||
distance_to_food = get_distance_between_objects(self, food_object)
|
||||
|
||||
if distance_to_food < self.max_visual_width and food_objects:
|
||||
self.energy += 140
|
||||
food_object.flag_for_death()
|
||||
# Use atomic consumption to prevent race conditions
|
||||
if food_object.try_claim():
|
||||
self.energy += 140
|
||||
food_object.flag_for_death()
|
||||
return self
|
||||
|
||||
if self.energy >= 1700:
|
||||
|
||||
@ -170,6 +170,8 @@ class World:
|
||||
next_buffer: int = 1 - self.current_buffer
|
||||
self.buffers[next_buffer].clear()
|
||||
|
||||
# Food claims auto-reset in FoodObject.tick()
|
||||
|
||||
for obj_list in self.buffers[self.current_buffer].values():
|
||||
for obj in obj_list:
|
||||
if obj.flags["death"]:
|
||||
@ -178,14 +180,17 @@ class World:
|
||||
interactable = self.query_objects_within_radius(
|
||||
obj.position.x, obj.position.y, obj.interaction_radius
|
||||
)
|
||||
interactable.remove(obj)
|
||||
# Create defensive copy to prevent shared state corruption
|
||||
interactable = interactable.copy()
|
||||
if obj in interactable:
|
||||
interactable.remove(obj)
|
||||
new_obj = obj.tick(interactable)
|
||||
else:
|
||||
new_obj = obj.tick()
|
||||
if new_obj is None:
|
||||
continue
|
||||
|
||||
# reproduction code
|
||||
# reproduction code - buffer new entities for atomic state transition
|
||||
if isinstance(new_obj, list):
|
||||
for item in new_obj:
|
||||
if isinstance(item, BaseEntity):
|
||||
@ -194,6 +199,8 @@ class World:
|
||||
else:
|
||||
cell = self._hash_position(new_obj.position)
|
||||
self.buffers[next_buffer][cell].append(new_obj)
|
||||
|
||||
# Atomic buffer switch - all state changes become visible simultaneously
|
||||
self.current_buffer = next_buffer
|
||||
|
||||
def add_object(self, new_object: BaseEntity) -> None:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user