Opt: Release cached assets

This commit is contained in:
LmeSzinc 2022-01-24 01:28:47 +08:00
parent 6d182bb620
commit b37114d505
7 changed files with 189 additions and 33 deletions

12
alas.py
View File

@ -315,30 +315,36 @@ class AzurLaneAutoScript:
Returns:
str: Name of the next task.
"""
from module.base.memory_opt import release_memory_after_task, release_memory_when_idle
release_memory_after_task()
task = self.config.get_next()
self.config.task = task
self.config.bind(task)
from module.base.resource import release_resources
if self.config.task.command != 'Alas':
release_resources(next_task=task.command)
if task.next_run > datetime.now():
release_memory_when_idle()
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()
release_resources()
self.wait_until(task.next_run)
self.run('start')
elif method == 'goto_main':
logger.info('Goto main page during wait')
self.run('goto_main')
release_resources()
self.wait_until(task.next_run)
elif method == 'stay_there':
logger.info('Stay there during wait')
release_resources()
self.wait_until(task.next_run)
else:
logger.warning(f'Invalid Optimization_WhenTaskQueueEmpty: {method}, fallback to stay_there')
release_resources()
self.wait_until(task.next_run)
AzurLaneConfig.is_hoarding_task = False

View File

@ -6,10 +6,11 @@ from PIL import ImageDraw
import module.config.server as server
from module.base.decorator import cached_property
from module.base.resource import Resource
from module.base.utils import *
class Button:
class Button(Resource):
def __init__(self, area, color, button, file=None, name=None):
"""Initialize a Button instance.
@ -50,6 +51,8 @@ class Button:
else:
self.is_gif = False
self.resource_add(key=self.file)
def __str__(self):
return self.name
@ -131,6 +134,10 @@ class Button:
self.image = load_image(self.file, self.area)
self._match_init = True
def resource_release(self):
self.image = None
self._match_init = False
def match(self, image, offset=30, threshold=0.85):
"""Detects button by template matching. To Some button, its location may not be static.

View File

@ -1,8 +1,7 @@
import random
import re
from functools import wraps
import numpy as np
from module.logger import logger
@ -63,7 +62,7 @@ class Config:
flag = [value is None or self.config.__getattribute__(key) == value
for key, value in record['options'].items()]
if not np.all(flag):
if not all(flag):
continue
return record['func'](self, *args, **kwargs)
@ -117,7 +116,7 @@ def function_drop(rate=0.5, default=None):
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if np.random.uniform(0, 1) > rate:
if random.uniform(0, 1) > rate:
return func(*args, **kwargs)
else:
cls = ''

142
module/base/resource.py Normal file
View File

@ -0,0 +1,142 @@
import re
import gc
import psutil
from module.base.decorator import cached_property
from module.logger import logger
def del_cached_property(obj, name):
"""
Delete a cached property safely.
Args:
obj:
name (str):
"""
if hasattr(obj, name):
del obj.__dict__[name]
def watch_memory(func):
"""
Show memory changes in log
release_resources: 181.555MB -> 163.066MB
"""
def wrapper(*args, **kwargs):
before = psutil.Process().memory_info().rss / (1024 * 1024)
result = func(*args, **kwargs)
after = psutil.Process().memory_info().rss / (1024 * 1024)
logger.info(f'{func.__name__}: {round(before, 3)}MB -> {round(after, 3)}MB')
return result
return wrapper
def get_assets_from_file(file, regex):
assets = set()
with open(file, 'r', encoding='utf-8') as f:
for row in f.readlines():
result = regex.search(row)
if result:
assets.add(result.group(1))
return assets
class PreservedAssets:
@cached_property
def ui(self):
assets = set()
assets |= get_assets_from_file(
file='./module/ui/assets.py',
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
)
assets |= get_assets_from_file(
file='./module/ui/ui.py',
regex=re.compile(r'\(([A-Z][A-Z0-9_]+),')
)
assets |= get_assets_from_file(
file='./module/handler/info_handler.py',
regex=re.compile(r'\(([A-Z][A-Z0-9_]+),')
)
# MAIN_CHECK == MAIN_GOTO_CAMPAIGN
assets.add('MAIN_GOTO_CAMPAIGN')
return assets
_preserved_assets = PreservedAssets()
class Resource:
instances = {}
def resource_add(self, key):
Resource.instances[key] = self
def resource_release(self):
pass
@classmethod
def is_loaded(cls, obj):
if hasattr(obj, '_image') and obj._image is None:
return False
elif hasattr(obj, 'image') and obj.image is None:
return False
return True
@classmethod
def resource_show(cls):
logger.hr('Show resource')
for key, obj in cls.instances.items():
if cls.is_loaded(obj):
continue
logger.info(f'{obj}: {key}')
def release_resources(next_task=''):
# Release all OCR models
# Usually to have 2 models loaded and each model takes about 20MB
# This will release 20-40MB
from module.ocr.ocr import OCR_MODEL
if 'Opsi' in next_task or 'commission' in next_task:
# OCR models will be used soon, don't release
models = []
elif next_task:
# Release OCR models except 'azur_lane'
models = ['cnocr', 'jp', 'tw']
else:
models = ['azur_lane', 'cnocr', 'jp', 'tw']
for model in models:
del_cached_property(OCR_MODEL, model)
# Release assets cache
# module.ui has about 80 assets and takes about 3MB
# Alas has about 800 assets, but they are not all loaded.
# Template images take more, about 6MB each
for key, obj in Resource.instances.items():
# Preserve assets for ui switching
if next_task and str(obj) in _preserved_assets.ui:
continue
# if Resource.is_loaded(obj):
# logger.info(f'Release {obj}')
obj.resource_release()
# Release cached images for map detection
from module.map_detection.utils_assets import ASSETS
attr_list = [
'ui_mask',
'ui_mask_os',
'ui_mask_stroke',
'ui_mask_in_map',
'ui_mask_os_in_map',
'tile_center_image',
'tile_corner_image',
'tile_corner_image_list'
]
for attr in attr_list:
del_cached_property(ASSETS, attr)
# Useless in most cases, but just call it
gc.collect()

View File

@ -5,11 +5,12 @@ import imageio
import module.config.server as server
from module.base.button import Button
from module.base.decorator import cached_property
from module.base.resource import Resource
from module.base.utils import *
from module.map_detection.utils import Points
class Template:
class Template(Resource):
def __init__(self, file):
"""
Args:
@ -21,6 +22,8 @@ class Template:
self.is_gif = os.path.splitext(self.file)[1] == '.gif'
self._image = None
self.resource_add(self.file)
@property
def image(self):
if self._image is None:
@ -28,9 +31,10 @@ class Template:
self._image = []
for image in imageio.mimread(self.file):
image = image[:, :, :3].copy() if len(image.shape) == 3 else image
image = self.pre_process(image)
self._image += [image, cv2.flip(image, 1)]
else:
self._image = load_image(self.file)
self._image = self.pre_process(load_image(self.file))
return self._image
@ -38,6 +42,19 @@ class Template:
def image(self, value):
self._image = value
def resource_release(self):
self._image = None
def pre_process(self, image):
"""
Args:
image (np.ndarray):
Returns:
np.ndarray:
"""
return image
@cached_property
def size(self):
if self.is_gif:

View File

@ -1,6 +1,5 @@
import numpy as np
from module.base.decorator import cached_property
from module.base.timer import Timer
from module.base.utils import red_overlay_transparency, get_color
from module.combat.combat import Combat
@ -9,23 +8,16 @@ from module.handler.info_handler import info_letter_preprocess
from module.logger import logger
from module.template.assets import *
TEMPLATE_AMBUSH_EVADE_SUCCESS.pre_process = info_letter_preprocess
TEMPLATE_AMBUSH_EVADE_FAILED.pre_process = info_letter_preprocess
TEMPLATE_MAP_WALK_OUT_OF_STEP.pre_process = info_letter_preprocess
class AmbushHandler(Combat):
MAP_AMBUSH_OVERLAY_TRANSPARENCY_THRESHOLD = 0.40
MAP_AIR_RAID_OVERLAY_TRANSPARENCY_THRESHOLD = 0.35 # Usually (0.50, 0.53)
MAP_AIR_RAID_CONFIRM_SECOND = 0.5
@cached_property
def _load_ambush_template(self):
TEMPLATE_AMBUSH_EVADE_SUCCESS.image = info_letter_preprocess(TEMPLATE_AMBUSH_EVADE_SUCCESS.image)
TEMPLATE_AMBUSH_EVADE_FAILED.image = info_letter_preprocess(TEMPLATE_AMBUSH_EVADE_FAILED.image)
return True
@cached_property
def _load_walk_template(self):
TEMPLATE_MAP_WALK_OUT_OF_STEP.image = info_letter_preprocess(TEMPLATE_MAP_WALK_OUT_OF_STEP.image)
return True
def ambush_color_initial(self):
MAP_AMBUSH.load_color(self.device.image)
MAP_AIR_RAID.load_color(self.device.image)
@ -52,7 +44,6 @@ class AmbushHandler(Combat):
def _handle_ambush_evade(self):
logger.info('Map ambushed')
_ = self._load_ambush_template
self.wait_until_appear_then_click(MAP_AMBUSH_EVADE)
self.wait_until_appear(INFO_BAR_1)
@ -119,7 +110,6 @@ class AmbushHandler(Combat):
if not self.appear(INFO_BAR_1):
return False
_ = self._load_walk_template
image = info_letter_preprocess(np.array(self.image_crop(INFO_BAR_DETECT)))
if TEMPLATE_MAP_WALK_OUT_OF_STEP.match(image):
logger.warning('Map walk out of step.')

View File

@ -1,7 +1,7 @@
from random import choice
import numpy as np
from module.base.decorator import cached_property
from module.base.timer import Timer
from module.combat.assets import GET_ITEMS_1
from module.handler.assets import INFO_BAR_DETECT
@ -12,6 +12,9 @@ from module.retire.dock import Dock, CARD_GRIDS
from module.template.assets import TEMPLATE_ENHANCE_SUCCESS, TEMPLATE_ENHANCE_FAILED, TEMPLATE_ENHANCE_IN_BATTLE
VALID_SHIP_TYPES = ['dd', 'ss', 'cl', 'ca', 'bb', 'cv', 'repair', 'others']
TEMPLATE_ENHANCE_SUCCESS.pre_process = info_letter_preprocess
TEMPLATE_ENHANCE_FAILED.pre_process = info_letter_preprocess
TEMPLATE_ENHANCE_IN_BATTLE.pre_process = info_letter_preprocess
class Enhancement(Dock):
@ -23,13 +26,6 @@ class Enhancement(Dock):
return 10
return 2000
@cached_property
def _load_enhance_template(self):
TEMPLATE_ENHANCE_SUCCESS.image = info_letter_preprocess(TEMPLATE_ENHANCE_SUCCESS.image)
TEMPLATE_ENHANCE_FAILED.image = info_letter_preprocess(TEMPLATE_ENHANCE_FAILED.image)
TEMPLATE_ENHANCE_IN_BATTLE.image = info_letter_preprocess(TEMPLATE_ENHANCE_IN_BATTLE.image)
return True
def _enhance_enter(self, favourite=False, ship_type=None):
"""
Pages:
@ -133,7 +129,6 @@ class Enhancement(Dock):
True if able to enhance otherwise False
Always paired with current ship_count
"""
_ = self._load_enhance_template
skip_until_ensured = True
enhanced = False
while 1: