Skip to content

Commit

Permalink
Merge pull request #2 from wortelus/caching
Browse files Browse the repository at this point in the history
Caching
  • Loading branch information
wortelus authored Jul 21, 2022
2 parents ec61225 + 1534801 commit 8a89b6c
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
__pycache__/
venv
/ms33558.ttf
img-cache.pkl
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ Instructions for **Windows**
running `python .\main.py` under the *xplane-streamdeck* directory,
while having the Stream Deck plugged in already

The program supports image caching, which saves several seconds of image preloading during launch
- To enable it, set `cache-path` field in `config.yaml`
- NOTE: If you are tweaking your image set or configuration, it is recommended disable this feature
to always see the up-to-date configuration state and avoid runtime errors

### Additional Info

![lower overhead](misc/lwrovhd.jpg)
Expand All @@ -97,7 +102,6 @@ while having the Stream Deck plugged in already
**Refer to the `B737-800X/README.md` for a guide on how to create/edit buttons.**

### What is planned / WIP?
- Caching of preloaded graphics
- More types of labels
- Multi deck support

Expand Down
3 changes: 3 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ xp-port: 49000
server-ip: 127.0.0.1
server-port: 49008
default-font: ms33558.ttf
# comment out following line to disable image caching
# the images will have to load for several seconds every time, but it is suitable for tweaking
cache-path: ./img-cache.pkl
stream-decks:
- serial: CL41I1A00651
keys: 32
Expand Down
44 changes: 42 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import math
import pickle
import threading
import time
from os.path import exists

import numpy as np
import yaml
Expand Down Expand Up @@ -203,12 +205,48 @@ def main():
"Have you installed it correctly?")
print(e)

cache_path = None
caching_enabled = False
load_cached_img = False
if "cache-path" in global_cfg:
cache_path = global_cfg["cache-path"]
# check cache_path if is not False or None -> implicating it is enabled in config
# and check if it exists
# then we will load the cache file, thus skipping the loader load_images_datarefs_all
if cache_path:
caching_enabled = True
if exists(global_cfg["cache-path"]):
load_cached_img = True

global presets_all
presets_all = preprocessing.load_all_presets(keys_dir, key_count)
presets_all = preprocessing.load_all_presets(keys_dir, key_count, preload_labels=load_cached_img)
global datarefs_all
datarefs_all = preprocessing.load_datarefs(presets_all)
global images_all
images_all = preprocessing.load_images_datarefs_all(current_deck, presets_all)

if not caching_enabled:
print("note: caching is disabled, loading will be noticeably slower")
print("you can enable it by setting the field 'cache-path' in config.yaml")
images_all = preprocessing.load_images_datarefs_all(current_deck, presets_all)
elif load_cached_img:
# images are stored as cache, open and load
print("cache file {} is present, skipping pre-generation.".format(cache_path))
print("note: if you changed configuration or icon set, you should delete the {} cache file".format(cache_path))
print("loading cache...")
with open(cache_path, 'rb') as f:
# load and convert it to runtime format
images_save_format = pickle.load(f)
images_all = preprocessing.convert_to_runtime_format(images_save_format)
else:
# caching is enabled, but cache file not found
print("cache file {} not found, starting the pre-generation.".format(cache_path))
images_all = preprocessing.load_images_datarefs_all(current_deck, presets_all)
# save images to cache-path
print("saving the pregen to {}".format(cache_path))
with open(cache_path, 'wb') as f:
# convert to save format and save it
images_save_format = preprocessing.convert_to_save_format(images_all)
pickle.dump(images_save_format, f)

global directory_stack
directory_stack = []
Expand All @@ -228,6 +266,8 @@ def main():
deck_show(current_deck, current_datarefs)
deck_show_static(current_deck)

print("xplane-streamdeck ready...")

try:
while True:
time.sleep(0.05)
Expand Down
49 changes: 44 additions & 5 deletions preprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def count_presets(target_dir):
return len(glob.glob1(target_dir, "*.yaml"))


def load_preset(target_dir, yaml_keyset, deck_key_count):
def load_preset(target_dir, yaml_keyset, deck_key_count, preload_labels=False):
with open(os.path.join(target_dir, yaml_keyset)) as stream:
try:
preset_cfg = safe_load(stream)
Expand Down Expand Up @@ -196,6 +196,19 @@ def load_preset(target_dir, yaml_keyset, deck_key_count):
key.get("gauge"),
key.get("display"),
)

# restoring images from cache file (preload_labels flag)
# this applies to buttons with 'label' parameter set
# If set to True, we must 'correct' image file_names here, because the
# image post-loader load_images_datarefs_all is not called during current session
if preload_labels:
btn = preset[index]
for i, state_name in enumerate(btn.file_names):
# change state name for storing, allowing same icons with different labels
if btn.label:
state_name = btn.label + state_name
preset[index].file_names[i] = state_name

if cmd_type == "dir":
other_keysets = np.append(other_keysets, name)

Expand All @@ -206,16 +219,18 @@ def add_yaml_suffix(filename):
return filename + ".yaml"


def load_all_presets(target_dir, deck_key_count):
def load_all_presets(target_dir, deck_key_count, preload_labels=False):
presets_all = {}
# read root
preset, keysets = load_preset(target_dir, ACTION_CFG, deck_key_count)
preset, keysets = load_preset(target_dir, ACTION_CFG, deck_key_count,
preload_labels=preload_labels)
presets_all[ACTION_CFG_NAME] = preset
# execute while there are keysets to be read and loaded into presets
while keysets.size > 0:
for _, key_set in enumerate(keysets):
if key_set not in presets_all and key_set != "return":
preset, other_keysets = load_preset(target_dir, add_yaml_suffix(key_set), deck_key_count)
preset, other_keysets = load_preset(target_dir, add_yaml_suffix(key_set), deck_key_count,
preload_labels=preload_labels)
presets_all[key_set] = preset
keysets = np.unique(np.concatenate((keysets, other_keysets), 0))

Expand Down Expand Up @@ -311,7 +326,9 @@ def load_images_datarefs(deck, presets_dir):
if state_name not in set_images:
state_image = render_key_image(deck, state_name, button.label)

# change state name for storing, allowing same icons with different labels
# change file_names in preset according to images_all, allowing same icons with different labels
# notice how this is executed in the post-processing stage
# i.e. after the presets have long been generated
if button.label:
state_name = button.label + state_name
button.file_names[i] = state_name
Expand All @@ -328,3 +345,25 @@ def load_images_datarefs_all(deck, presets_all):
set_images_all.update(images_single_dir)

return set_images_all

#
# pickle helpers
#

# pickle is unable to handler 'memoryview' objects, we must convert them to bytearray and vice versa


def convert_to_save_format(images_all):
images_bytearray = {}
for key, img in images_all.items():
images_bytearray[key] = img.tobytes()

return images_bytearray


def convert_to_runtime_format(images_save_format):
images_all = {}
for key, img in images_save_format.items():
images_all[key] = memoryview(img)

return images_all

0 comments on commit 8a89b6c

Please sign in to comment.