Open wojciechsura opened 3 weeks ago
Can you post some soft of reproducible example (maybe a little one-file console application?)
I don't have minimal example, but you can reproduce the issue in the following way:
public override void UpdateVisuals(List<KeyboardElementVisuals> buttons, List<KeyboardElementVisuals> encoders)
{
if (deck == null)
return;
if (sleep)
{
deck.SetBrightness(brightness);
sleep = false;
}
// Prepare images to display
for (int i = 0; i < buttons.Count; i++)
{
deck.SetKeyBitmap(i, buttons[i].CustomCache as KeyBitmap);
}
}
The last line is the place where buttons are set from local (application) cache.
I know the project is moderately big, but there are only two files you may be interested in, related to the issue.
OpenMacroKeyboard.Drivers.StreamDeck\Driver.cs
- contains the code, which creates KeyImage
caches and later uses them to set images to buttons (methods BuildElementCustomCache
, which creates the cache and UpdateVisuals
, which uses it);OpenMacroKeyboard.BusinessLogic\Services\DriverRepository\MacroKeyboard.cs
, method BuildCustomCacheRecursive
- initiates the cache building process and stores objects returned by the driver for later usage. I haven't had time to try it with my stream deck v2 but I read a bit through the code and I'm pretty sure that you pass something that's actually null
(which is not allowed). There is currently not an explicit null-check for this method (I should probably add one) but it will crash later down the line if you pass null to SetKeyBitmap.
// if buttons[i].CustomCache is not a KeyBitmap or null than this will throw!
deck.SetKeyBitmap(i, buttons[i].CustomCache as KeyBitmap);
CustomCache
is if type object?
so basically anything can be stored in that property. It's only assigned in the constructor of KeyboardElementVisuals
but everything in this class is nullable, and there are multiple instances that instantiate the class with new KeyboardElementVisuals(null, null, null, null)
and the only instantiation where there even could be a non-null value is inside ElementInfo
where another CustomCache
property of type object?
is assigned by a constructor with a value b?.CustomCache
with a null-conditional operator.
I actually stopped at that point because there are so many possibilities where this value could end up being null.
My guess is that something inside your caching code doesn't work as expected/intended and leads to null values inside CustomCache
. A "quick-fix" would be to take the code inside the driver and handle the null case there.
// Note: just typed this in GitHub and have not compiled/tested it.
// Prepare images to display
for (int i = 0; i < buttons.Count; i++)
{
if (buttons[i].CustomCache is KeyBitmap kb)
{
deck.SetKeyBitmap(i, kb);
}
else
{
// CustomCache was null or not of type KeyBitmap
// throw Exception here or set break-point to diagnose the issue
// or log the error event
// or fall back to a different (known) KeyBitmap that is actually non-null
}
}
On the long run you should try to be way stricter with all of your types. There should be very good reasons to use object?
and if you do should should take extra precautions that the type and value stored in such properties/variables are the ones you expect. You've enabled nullable-reference types (which is great) but you should try to keep most things non-nullable and design abstractions differently.
I created and stored a number of
KeyImage
images to improve performance during switching screens. However, during using such images, I ran into an exception:Workaround: Disable caching when creating Stream Deck device instance. Also. don't store
KeyBitmap
s, create them every time they are needed.