Add: Goto main page when task queue is empty

- Refactor: Task waiting
- Fix: Saving config twice in nested multiset wrapper
This commit is contained in:
LmeSzinc 2021-11-14 00:12:43 +08:00
parent 5e2d5d7444
commit e984859ad6
12 changed files with 141 additions and 80 deletions

56
alas.py
View File

@ -110,6 +110,10 @@ class AzurLaneAutoScript:
from module.handler.login import LoginHandler from module.handler.login import LoginHandler
LoginHandler(self.config, device=self.device).app_restart() LoginHandler(self.config, device=self.device).app_restart()
def goto_main(self):
from module.ui.ui import UI
UI(self.config, device=self.device).ui_goto_main()
def research(self): def research(self):
from module.research.research import RewardResearch from module.research.research import RewardResearch
RewardResearch(config=self.config, device=self.device).run() RewardResearch(config=self.config, device=self.device).run()
@ -250,6 +254,49 @@ class AzurLaneAutoScript:
GemsFarming(config=self.config, device=self.device).run( GemsFarming(config=self.config, device=self.device).run(
name=self.config.Campaign_Name, folder=self.config.Campaign_Event, mode=self.config.Campaign_Mode) name=self.config.Campaign_Name, folder=self.config.Campaign_Event, mode=self.config.Campaign_Mode)
@staticmethod
def wait_until(future):
"""
Wait until a specific time.
Args:
future (datetime):
"""
seconds = future.timestamp() - datetime.now().timestamp() + 1
if seconds > 0:
time.sleep(seconds)
else:
logger.warning(f'Wait until {str(future)}, but sleep length < 0, skip waiting')
def get_next_task(self):
"""
Returns:
str: Name of the next task.
"""
task = self.config.get_next()
self.config.task = task
self.config.bind(task)
if task.next_run > datetime.now():
logger.info(f'Wait until {task.next_run} for task `{task.command}`')
method = self.config.Optimization_WhenTaskQueueEmpty
if method == 'close_game':
logger.info('Close game during wait')
self.device.app_stop()
self.wait_until(task.next_run)
self.run('restart')
elif method == 'goto_main':
logger.info('Goto main page during wait')
self.run('goto_main')
self.wait_until(task.next_run)
elif method == 'stay_there':
self.wait_until(task.next_run)
else:
logger.warning(f'Invalid Optimization_WhenTaskQueueEmpty: {method}, fallback to stay_there')
self.wait_until(task.next_run)
return task.command
def loop(self): def loop(self):
logger.set_file_logger(self.config_name) logger.set_file_logger(self.config_name)
logger.info(f'Start scheduler loop: {self.config_name}') logger.info(f'Start scheduler loop: {self.config_name}')
@ -257,14 +304,16 @@ class AzurLaneAutoScript:
failure_record = {} failure_record = {}
while 1: while 1:
task = self.get_next_task()
# Skip first restart # Skip first restart
if is_first and self.config.task == 'Restart': if is_first and task == 'Restart':
logger.info('Skip task `Restart` at scheduler start') logger.info('Skip task `Restart` at scheduler start')
self.config.task_delay(server_update=True) self.config.task_delay(server_update=True)
del self.__dict__['config'] del self.__dict__['config']
continue
# Run # Run
task = self.config.task
logger.info(f'Scheduler: Start task `{task}`') logger.info(f'Scheduler: Start task `{task}`')
self.device.stuck_record_clear() self.device.stuck_record_clear()
self.device.click_record_clear() self.device.click_record_clear()
@ -272,7 +321,6 @@ class AzurLaneAutoScript:
logger.hr(task, level=0) logger.hr(task, level=0)
success = self.run(inflection.underscore(task)) success = self.run(inflection.underscore(task))
logger.info(f'Scheduler: End task `{task}`') logger.info(f'Scheduler: End task `{task}`')
del self.__dict__['config']
is_first = False is_first = False
# Check failures # Check failures
@ -289,9 +337,11 @@ class AzurLaneAutoScript:
exit(1) exit(1)
if success: if success:
del self.__dict__['config']
continue continue
elif self.config.Error_HandleError: elif self.config.Error_HandleError:
# self.config.task_delay(success=False) # self.config.task_delay(success=False)
del self.__dict__['config']
continue continue
else: else:
break break

View File

@ -15,7 +15,7 @@
"Optimization": { "Optimization": {
"CombatScreenshotInterval": 1.0, "CombatScreenshotInterval": 1.0,
"TaskHoardingDuration": 0, "TaskHoardingDuration": 0,
"CloseGameDuringWait": false "WhenTaskQueueEmpty": "goto_main"
}, },
"DropRecord": { "DropRecord": {
"SaveFolder": "./screenshots", "SaveFolder": "./screenshots",

View File

@ -61,9 +61,14 @@
"type": "input", "type": "input",
"value": 0 "value": 0
}, },
"CloseGameDuringWait": { "WhenTaskQueueEmpty": {
"type": "checkbox", "type": "select",
"value": false "value": "goto_main",
"option": [
"stay_there",
"goto_main",
"close_game"
]
} }
}, },
"DropRecord": { "DropRecord": {

View File

@ -30,7 +30,9 @@ Error:
Optimization: Optimization:
CombatScreenshotInterval: 1.0 CombatScreenshotInterval: 1.0
TaskHoardingDuration: 0 TaskHoardingDuration: 0
CloseGameDuringWait: false WhenTaskQueueEmpty:
value: goto_main
option: [stay_there, goto_main, close_game]
DropRecord: DropRecord:
SaveFolder: ./screenshots SaveFolder: ./screenshots
AzurStatsID: null AzurStatsID: null

View File

@ -1,6 +1,6 @@
import copy
import datetime import datetime
import operator import operator
import time
from module.base.filter import Filter from module.base.filter import Filter
from module.base.utils import ensure_time from module.base.utils import ensure_time
@ -23,11 +23,26 @@ class Function:
self.next_run = deep_get(data, keys='Scheduler.NextRun', default=datetime(2020, 1, 1, 0, 0)) self.next_run = deep_get(data, keys='Scheduler.NextRun', default=datetime(2020, 1, 1, 0, 0))
def __str__(self): def __str__(self):
return f'{self.command} ({self.enable}, {str(self.next_run)})' enable = 'Enable' if self.enable else 'Disable'
return f'{self.command} ({enable}, {str(self.next_run)})'
__repr__ = __str__ __repr__ = __str__
def name_to_function(name):
"""
Args:
name (str):
Returns:
Function:
"""
function = Function({})
function.command = name
function.enable = True
return function
class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig): class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
bound = {} bound = {}
@ -66,17 +81,21 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
self.waiting_task = [] self.waiting_task = []
# Task to run and bind. # Task to run and bind.
# Task means the name of the function to run in AzurLaneAutoScript class. # Task means the name of the function to run in AzurLaneAutoScript class.
self.task: Function
if config_name == 'template': if config_name == 'template':
# For dev tools # For dev tools
logger.info('Using template config, which is read only') logger.info('Using template config, which is read only')
self.auto_update = False self.auto_update = False
self.task = 'template' self.task = name_to_function('template')
else: else:
self.load() self.load()
if task is None: if task is None:
task = self.get_next() # Bind `Alas` by default which includes emulator settings.
task = name_to_function('Alas')
else:
# Bind a specific task for debug purpose.
task = name_to_function(task)
self.bind(task) self.bind(task)
logger.info(f'Bind task {task}')
self.task = task self.task = task
def load(self): def load(self):
@ -89,11 +108,14 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
def bind(self, func): def bind(self, func):
""" """
Args: Args:
func (str): Function to run func (str, Function): Function to run
""" """
if isinstance(func, Function):
func = func.command
func_set = {func, 'General', 'Alas'} func_set = {func, 'General', 'Alas'}
if 'opsi' in func.lower(): if 'opsi' in func.lower():
func_set.add('OpsiGeneral') func_set.add('OpsiGeneral')
logger.info(f'Bind task {func_set}')
# Bind arguments # Bind arguments
visited = set() visited = set()
@ -116,7 +138,8 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
@property @property
def hoarding(self): def hoarding(self):
return timedelta(minutes=deep_get(self.data, keys='Alas.Optimization.TaskHoardingDuration', default=0)) minutes = int(deep_get(self.data, keys='Alas.Optimization.TaskHoardingDuration', default=0))
return timedelta(minutes=max(minutes, 0))
@property @property
def close_game(self): def close_game(self):
@ -151,35 +174,25 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
def get_next(self): def get_next(self):
""" """
Returns: Returns:
str: Command to run Function: Command to run
""" """
self.get_next_task() self.get_next_task()
if self.pending_task: if self.pending_task:
AzurLaneConfig.is_hoarding_task = False AzurLaneConfig.is_hoarding_task = False
pending = [f.command for f in self.pending_task] logger.info(f'Pending tasks: {[f.command for f in self.pending_task]}')
logger.info(f'Pending tasks: {pending}') task = self.pending_task[0]
self.pending_task = pending
task = pending[0]
logger.attr('Task', task) logger.attr('Task', task)
return task return task
else: else:
AzurLaneConfig.is_hoarding_task = True AzurLaneConfig.is_hoarding_task = True
if self.waiting_task: if self.waiting_task:
task = self.waiting_task[0]
target = (task.next_run + self.hoarding).replace(microsecond=0)
logger.info('No task pending') logger.info('No task pending')
logger.info(f'Wait until {target} for task `{task.command}`') task = copy.deepcopy(self.waiting_task[0])
if self.close_game: task.next_run = (task.next_run + self.hoarding).replace(microsecond=0)
self.modified['Restart.Scheduler.Enable'] = True logger.attr('Task', task)
self.modified['Restart.Scheduler.NextRun'] = target return task
self.task = 'Restart'
self.update()
return 'Restart'
else:
self.wait_until(target)
return self.get_next()
else: else:
logger.critical('No task waiting or pending') logger.critical('No task waiting or pending')
logger.critical('Please enable at least one task') logger.critical('Please enable at least one task')
@ -277,7 +290,7 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
kv = dict_to_kv( kv = dict_to_kv(
{'success': success, 'server_update': server_update, 'target': target, 'minute': minute}, {'success': success, 'server_update': server_update, 'target': target, 'minute': minute},
allow_none=False) allow_none=False)
logger.info(f'Delay task `{self.task}` to {run} ({kv})') logger.info(f'Delay task `{self.task.command}` to {run} ({kv})')
self.Scheduler_NextRun = run self.Scheduler_NextRun = run
else: else:
raise ScriptError('Missing argument in delay_next_run, should set at least one') raise ScriptError('Missing argument in delay_next_run, should set at least one')
@ -324,9 +337,9 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
Raises: Raises:
bool: If task switched bool: If task switched
""" """
prev = self.task prev = self.task.command
self.load() self.load()
new = self.get_next() new = self.get_next().command
if prev != new: if prev != new:
logger.info(f'Switch task `{prev}` to `{new}`') logger.info(f'Switch task `{prev}` to `{new}`')
return True return True
@ -343,20 +356,6 @@ class AzurLaneConfig(ConfigUpdater, ManualConfig, GeneratedConfig):
if self.task_switched(): if self.task_switched():
self.task_stop(message=message) self.task_stop(message=message)
@staticmethod
def wait_until(future):
"""
Wait until a specific time.
Args:
future (datetime):
"""
seconds = future.timestamp() - datetime.now().timestamp() + 1
if seconds > 0:
time.sleep(seconds)
else:
logger.warning(f'Wait until {str(future)}, but sleep length < 0, skip waiting')
@property @property
def campaign_name(self): def campaign_name(self):
""" """
@ -497,14 +496,19 @@ class MultiSetWrapper:
main (AzurLaneConfig): main (AzurLaneConfig):
""" """
self.main = main self.main = main
self.in_wrapper = False
def __enter__(self): def __enter__(self):
self.main.auto_update = False if self.main.auto_update:
self.main.auto_update = False
else:
self.in_wrapper = True
return self return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
self.main.update() if not self.in_wrapper:
self.main.auto_update = True self.main.update()
self.main.auto_update = True
class ConfigTypeChecker: class ConfigTypeChecker:

View File

@ -32,7 +32,7 @@ class GeneratedConfig:
# Group `Optimization` # Group `Optimization`
Optimization_CombatScreenshotInterval = 1.0 Optimization_CombatScreenshotInterval = 1.0
Optimization_TaskHoardingDuration = 0 Optimization_TaskHoardingDuration = 0
Optimization_CloseGameDuringWait = False Optimization_WhenTaskQueueEmpty = 'goto_main' # stay_there, goto_main, close_game
# Group `DropRecord` # Group `DropRecord`
DropRecord_SaveFolder = './screenshots' DropRecord_SaveFolder = './screenshots'

View File

@ -296,9 +296,12 @@
"name": "Hoard Tasks For X Minute(s)", "name": "Hoard Tasks For X Minute(s)",
"help": "By purposely not adding ready tasks to pending, allows for larger subsets to be built and run en masse at a later time\nCan reduce the frequency of operating AL" "help": "By purposely not adding ready tasks to pending, allows for larger subsets to be built and run en masse at a later time\nCan reduce the frequency of operating AL"
}, },
"CloseGameDuringWait": { "WhenTaskQueueEmpty": {
"name": "Close Game During Wait", "name": "When Task Queue is Empty",
"help": "Close AL when there are no pending tasks, can help reduce CPU" "help": "Close AL when there are no pending tasks, can help reduce CPU",
"stay_there": "Stay There",
"goto_main": "Goto Main Page",
"close_game": "Close Game"
} }
}, },
"DropRecord": { "DropRecord": {

View File

@ -296,9 +296,12 @@
"name": "Optimization.TaskHoardingDuration.name", "name": "Optimization.TaskHoardingDuration.name",
"help": "Optimization.TaskHoardingDuration.help" "help": "Optimization.TaskHoardingDuration.help"
}, },
"CloseGameDuringWait": { "WhenTaskQueueEmpty": {
"name": "Optimization.CloseGameDuringWait.name", "name": "Optimization.WhenTaskQueueEmpty.name",
"help": "Optimization.CloseGameDuringWait.help" "help": "Optimization.WhenTaskQueueEmpty.help",
"stay_there": "stay_there",
"goto_main": "goto_main",
"close_game": "close_game"
} }
}, },
"DropRecord": { "DropRecord": {

View File

@ -296,9 +296,12 @@
"name": "囤积任务 X 分钟", "name": "囤积任务 X 分钟",
"help": "能在收菜期间降低操作游戏的频率\n任务触发后等待 X 分钟,再一次性执行囤积的任务" "help": "能在收菜期间降低操作游戏的频率\n任务触发后等待 X 分钟,再一次性执行囤积的任务"
}, },
"CloseGameDuringWait": { "WhenTaskQueueEmpty": {
"name": "无任务时关闭游戏", "name": "当任务队列清空后",
"help": "能在收菜期间降低 CPU 占用" "help": "无任务时关闭游戏,能在收菜期间降低 CPU 占用",
"stay_there": "停在原处",
"goto_main": "前往主界面",
"close_game": "关闭游戏"
} }
}, },
"DropRecord": { "DropRecord": {

View File

@ -296,9 +296,12 @@
"name": "囤積任務 X 分鐘", "name": "囤積任務 X 分鐘",
"help": "能在收穫期間降低操作遊戲的頻率\n任務觸發後等待 X 分鐘後,一次性執行佇列中的任務" "help": "能在收穫期間降低操作遊戲的頻率\n任務觸發後等待 X 分鐘後,一次性執行佇列中的任務"
}, },
"CloseGameDuringWait": { "WhenTaskQueueEmpty": {
"name": "無任務時關閉遊戲", "name": "當任務隊列清空後",
"help": "能在收穫期間降低 CPU 使用率" "help": "無任務時關閉遊戲,能在收菜期間降低 CPU 佔用",
"stay_there": "停在原處",
"goto_main": "前往主界面",
"close_game": "關閉遊戲"
} }
}, },
"DropRecord": { "DropRecord": {

View File

@ -123,7 +123,9 @@ class Device(Screenshot, Control, AppControl):
def app_start(self): def app_start(self):
super().app_start() super().app_start()
self.stuck_record_clear() self.stuck_record_clear()
self.click_record_clear()
def app_stop(self): def app_stop(self):
super().app_stop() super().app_stop()
self.stuck_record_clear() self.stuck_record_clear()
self.click_record_clear()

View File

@ -1,5 +1,4 @@
from datetime import datetime, timedelta from datetime import datetime
import time
import module.config.server as server import module.config.server as server
from module.base.timer import Timer from module.base.timer import Timer
@ -101,19 +100,6 @@ class LoginHandler(Combat):
def app_restart(self): def app_restart(self):
logger.hr('App restart') logger.hr('App restart')
self.device.app_stop() self.device.app_stop()
now = datetime.now()
target = self.config.Scheduler_NextRun
if target > now:
task = 'unknown'
for waiting in self.config.waiting_task:
if waiting.command != 'Restart':
task = waiting.command
break
logger.info(f'{self.config.Emulator_PackageName} will be started at {target} for task `{task}`')
self.config.wait_until(target)
self.device.app_start() self.device.app_start()
self.handle_app_login() self.handle_app_login()
# self.ensure_no_unfinished_campaign() # self.ensure_no_unfinished_campaign()