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