Get hot reloading working again
This commit is contained in:
+104
-31
@@ -378,36 +378,44 @@ def bot(global_state):
|
||||
print('Bot module loaded.')
|
||||
|
||||
|
||||
def start_automation(global_state):
|
||||
"""Start migrated path-following and crop automation only."""
|
||||
def start_automation(global_state, initialize=True):
|
||||
"""Start or replace the movement worker without replacing the connection."""
|
||||
g = global_state
|
||||
g.protocol_version = 777
|
||||
g.time = 0.0
|
||||
g.path = []
|
||||
g.look_at = None
|
||||
g.y_v = 0.0
|
||||
g.y_a = 0.0
|
||||
g.crawling = False
|
||||
g.afk_timeout = 0.0
|
||||
g.objects = g.get('objects') or {}
|
||||
g.players = g.get('players') or {}
|
||||
g.breaking = None
|
||||
g.break_time = 0.0
|
||||
g.correction_count = g.get('correction_count', 0)
|
||||
g.yaw = float(g.get('yaw', 0.0) or 0.0)
|
||||
g.pitch = float(g.get('pitch', 0.0) or 0.0)
|
||||
g.chunks.loading = False
|
||||
# Readiness below is based on the bot's own chunk and the next two path
|
||||
# steps, never on unrelated holes elsewhere in the view cache.
|
||||
g.movement_paused = False
|
||||
g.movement_status = 'idle'
|
||||
g.movement_error = None
|
||||
# Keep the port constrained to crop harvesting; other legacy jobs and
|
||||
# general inventory/window mutations are not exposed through this adapter.
|
||||
from mosfet.game import Game
|
||||
from mosfet.job import JobStates
|
||||
g.game = Game(g)
|
||||
g.job = JobStates(g)
|
||||
if initialize:
|
||||
g.time = 0.0
|
||||
g.path = []
|
||||
g.look_at = None
|
||||
g.y_v = 0.0
|
||||
g.y_a = 0.0
|
||||
g.crawling = False
|
||||
g.afk_timeout = 0.0
|
||||
g.objects = g.get('objects') or {}
|
||||
g.players = g.get('players') or {}
|
||||
g.breaking = None
|
||||
g.break_time = 0.0
|
||||
g.correction_count = g.get('correction_count', 0)
|
||||
g.yaw = float(g.get('yaw', 0.0) or 0.0)
|
||||
g.pitch = float(g.get('pitch', 0.0) or 0.0)
|
||||
g.chunks.loading = False
|
||||
# Readiness below is based on nearby chunks, not unrelated view holes.
|
||||
g.movement_paused = False
|
||||
g.movement_status = 'idle'
|
||||
g.movement_error = None
|
||||
# Keep the active port constrained to the migrated job subset.
|
||||
from mosfet.game import Game
|
||||
from mosfet.job import JobStates
|
||||
g.game = Game(g)
|
||||
g.job = JobStates(g)
|
||||
|
||||
old_stop = g.get('movement_stop_event')
|
||||
old_worker = g.get('movement_thread')
|
||||
if old_stop is not None:
|
||||
old_stop.set()
|
||||
if old_worker is not None and old_worker is not threading.current_thread():
|
||||
old_worker.join(timeout=1.0)
|
||||
stop_event = threading.Event()
|
||||
g.movement_stop_event = stop_event
|
||||
|
||||
def run():
|
||||
from mosfet import bot, utils
|
||||
@@ -419,8 +427,8 @@ def start_automation(global_state):
|
||||
print('[movement] status={}'.format(status), flush=True)
|
||||
g.movement_status = status
|
||||
|
||||
while (g.get('running', True) and g.get('connection') and
|
||||
g.connection.connected):
|
||||
while (not stop_event.is_set() and g.get('running', True) and
|
||||
g.get('connection') and g.connection.connected):
|
||||
started = time.monotonic()
|
||||
now = time.monotonic()
|
||||
if not initial_world_ready:
|
||||
@@ -443,7 +451,12 @@ def start_automation(global_state):
|
||||
set_status('waiting_for_chunks')
|
||||
elif has_work and movement_ready:
|
||||
try:
|
||||
bot.tick(g)
|
||||
reload_lock = g.get('reload_lock')
|
||||
if reload_lock is None:
|
||||
bot.tick(g)
|
||||
else:
|
||||
with reload_lock:
|
||||
bot.tick(g)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
g.movement_error = traceback.format_exc()
|
||||
@@ -462,3 +475,63 @@ def start_automation(global_state):
|
||||
g.movement_thread = worker
|
||||
worker.start()
|
||||
return worker
|
||||
|
||||
|
||||
def _remove_instance_listeners(connection, instance):
|
||||
if connection is None or instance is None:
|
||||
return
|
||||
for name in ('packet_listeners', 'early_packet_listeners',
|
||||
'outgoing_packet_listeners',
|
||||
'early_outgoing_packet_listeners'):
|
||||
listeners = getattr(connection, name, [])
|
||||
listeners[:] = [listener for listener in listeners
|
||||
if getattr(getattr(listener, 'callback', None),
|
||||
'__self__', None) is not instance]
|
||||
|
||||
|
||||
def refresh_runtime(global_state, reload_managers=False):
|
||||
"""Rebind reloadable runtime adapters without touching the connection."""
|
||||
g = global_state
|
||||
connection = g.get('connection')
|
||||
|
||||
_remove_instance_listeners(connection, g.get('chat'))
|
||||
_remove_instance_listeners(connection, g.get('game'))
|
||||
if reload_managers:
|
||||
_remove_instance_listeners(connection, g.get('entities'))
|
||||
_remove_instance_listeners(connection, g.get('inventory'))
|
||||
|
||||
if reload_managers:
|
||||
from mosfet.protocol import managers
|
||||
if connection and g.get('chunks'):
|
||||
current = g.chunks
|
||||
replacement = managers.WorldState(current.min_y)
|
||||
replacement.__dict__.update(current.__dict__)
|
||||
g.chunks = replacement
|
||||
g.world_state = replacement
|
||||
connection.world_state = replacement
|
||||
from mosfet.info import blocks as block_data
|
||||
from mosfet.info import items as item_data
|
||||
g.mcdata = DataManager()
|
||||
g.entities = managers.EntityManager(g)
|
||||
g.inventory = managers.InventoryManager(g)
|
||||
# Keep data references in the reloaded legacy Game adapter current.
|
||||
_ = (block_data, item_data)
|
||||
|
||||
from mosfet.protocol.modern import ModernChatManager
|
||||
g.chat = ModernChatManager(g)
|
||||
g.world = world.World(g)
|
||||
g.game = game.Game(g)
|
||||
|
||||
state_name = getattr(g.get('job'), 'state', None)
|
||||
state_name = getattr(state_name, '__name__', None)
|
||||
g.job = job.JobStates(g)
|
||||
if state_name and state_name != 'idle':
|
||||
replacement_state = getattr(g.job, state_name, None)
|
||||
if replacement_state is not None:
|
||||
g.job.state = replacement_state
|
||||
g.commands = commands.Commands(g)
|
||||
if not reload_managers:
|
||||
g.mcdata = DataManager()
|
||||
start_automation(g, initialize=False)
|
||||
print('[hot reload] runtime adapters refreshed; connection kept alive',
|
||||
flush=True)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Chat adapter for the Minecraft 26.3 play protocol."""
|
||||
import time
|
||||
from contextlib import nullcontext
|
||||
|
||||
from minecraft.networking.modern import (
|
||||
ChatCommand, ClientboundPlayerChatPacket, ServerboundChatPacket, SystemChat,
|
||||
@@ -21,11 +22,13 @@ class ModernChatManager:
|
||||
text = '{}: {}'.format(sender, packet.message)
|
||||
print('[chat] {}: {}'.format(sender, packet.message), flush=True)
|
||||
if self.handler:
|
||||
self.handler(('CHAT', sender, text))
|
||||
with self.g.get('reload_lock') or nullcontext():
|
||||
self.handler(('CHAT', sender, text))
|
||||
|
||||
def handle_system_chat(self, packet, message):
|
||||
if self.handler:
|
||||
self.handler(('SYSTEM', None, message))
|
||||
with self.g.get('reload_lock') or nullcontext():
|
||||
self.handler(('SYSTEM', None, message))
|
||||
|
||||
def send(self, message):
|
||||
if not message:
|
||||
|
||||
Reference in New Issue
Block a user