Refactor uploading animations to allow uploading automatically instead of having a separate upload button, and iron out some inconsistencies.
This commit is contained in:
parent
74f94c5bf5
commit
ec061d4223
@ -188,10 +188,10 @@ struct AnimationStateForPad
|
|||||||
char *out = &result[panel*iBytesPerPanel];
|
char *out = &result[panel*iBytesPerPanel];
|
||||||
|
|
||||||
// Add the released animation, then overlay the pressed animation if we're pressed.
|
// Add the released animation, then overlay the pressed animation if we're pressed.
|
||||||
OverlayLights(out, animations[panel][SMX_LightsType_Released].GetAnimationFrame());
|
OverlayLights(out, animations[SMX_LightsType_Released][panel].GetAnimationFrame());
|
||||||
bool bPressed = bool(iPadState & (1 << panel));
|
bool bPressed = bool(iPadState & (1 << panel));
|
||||||
if(bPressed && bUsePressedAnimations)
|
if(bPressed && bUsePressedAnimations)
|
||||||
OverlayLights(out, animations[panel][SMX_LightsType_Pressed].GetAnimationFrame());
|
OverlayLights(out, animations[SMX_LightsType_Pressed][panel].GetAnimationFrame());
|
||||||
else if(bPressed && !bUsePressedAnimations)
|
else if(bPressed && !bUsePressedAnimations)
|
||||||
{
|
{
|
||||||
// Light all LEDs on this panel using stepColor.
|
// Light all LEDs on this panel using stepColor.
|
||||||
@ -217,7 +217,7 @@ struct AnimationStateForPad
|
|||||||
}
|
}
|
||||||
|
|
||||||
// State for both animations on each panel:
|
// State for both animations on each panel:
|
||||||
AnimationState animations[9][NUM_SMX_LightsType];
|
AnimationState animations[NUM_SMX_LightsType][9];
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@ -283,14 +283,6 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the SMXPanelAnimation. The rest of the animation state is internal.
|
|
||||||
SMXPanelAnimation SMXPanelAnimation::GetLoadedAnimation(int pad, int panel, SMX_LightsType type)
|
|
||||||
{
|
|
||||||
g_Lock.AssertNotLockedByCurrentThread();
|
|
||||||
LockMutex L(g_Lock);
|
|
||||||
return pad_states[pad].animations[panel][type].animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load an array of animation frames as a panel animation. Each frame must
|
// Load an array of animation frames as a panel animation. Each frame must
|
||||||
// be 14x15 or 23x24.
|
// be 14x15 or 23x24.
|
||||||
void SMXPanelAnimation::Load(const vector<SMXGif::SMXGifFrame> &frames, int panel)
|
void SMXPanelAnimation::Load(const vector<SMXGif::SMXGifFrame> &frames, int panel)
|
||||||
@ -342,6 +334,8 @@ void SMXPanelAnimation::Load(const vector<SMXGif::SMXGifFrame> &frames, int pane
|
|||||||
m_iLoopFrame = 0;
|
m_iLoopFrame = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "SMXPanelAnimationUpload.h"
|
||||||
|
|
||||||
// Load a GIF into SMXLoadedPanelAnimations::animations.
|
// Load a GIF into SMXLoadedPanelAnimations::animations.
|
||||||
bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType type, const char **error)
|
bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType type, const char **error)
|
||||||
{
|
{
|
||||||
@ -362,15 +356,24 @@ bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the graphics into SMXPanelAnimations.
|
||||||
|
SMXPanelAnimation animations[9];
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
animations[panel].Load(frames, panel);
|
||||||
|
|
||||||
|
// Set up the upload for this graphic.
|
||||||
|
if(!SMX_LightsUpload_PrepareUpload(pad, type, animations, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
// Lock while we access pad_states.
|
// Lock while we access pad_states.
|
||||||
g_Lock.AssertNotLockedByCurrentThread();
|
g_Lock.AssertNotLockedByCurrentThread();
|
||||||
LockMutex L(g_Lock);
|
LockMutex L(g_Lock);
|
||||||
|
|
||||||
// Load the animation for each panel into SMXPanelAnimations.
|
// Commit the animation to pad_states now that we know there are no errors.
|
||||||
for(int panel = 0; panel < 9; ++panel)
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
{
|
{
|
||||||
SMXPanelAnimation &animation = pad_states[pad].animations[panel][type].animation;
|
SMXPanelAnimation &animation = pad_states[pad].animations[type][panel].animation;
|
||||||
animation.Load(frames, panel);
|
animation = animations[panel];
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -434,26 +437,27 @@ private:
|
|||||||
for(int panel = 0; panel < 9; ++panel)
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
{
|
{
|
||||||
// The released animation is always playing.
|
// The released animation is always playing.
|
||||||
pad_state.animations[panel][SMX_LightsType_Released].Play();
|
pad_state.animations[SMX_LightsType_Released][panel].Play();
|
||||||
|
|
||||||
// The pressed animation only plays while the button is pressed,
|
// The pressed animation only plays while the button is pressed,
|
||||||
// and rewind when it's released.
|
// and rewind when it's released.
|
||||||
bool bPressed = iPadState & (1 << panel);
|
bool bPressed = iPadState & (1 << panel);
|
||||||
if(bPressed)
|
if(bPressed)
|
||||||
pad_state.animations[panel][SMX_LightsType_Pressed].Play();
|
pad_state.animations[SMX_LightsType_Pressed][panel].Play();
|
||||||
else
|
else
|
||||||
pad_state.animations[panel][SMX_LightsType_Pressed].Stop();
|
pad_state.animations[SMX_LightsType_Pressed][panel].Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current state.
|
// Set the current state.
|
||||||
asLightsDataOut = pad_state.GetLightsCommand(iPadState, config);
|
asLightsDataOut = pad_state.GetLightsCommand(iPadState, config);
|
||||||
|
|
||||||
// Advance animations.
|
// Advance animations.
|
||||||
for(int panel = 0; panel < 9; ++panel)
|
for(int type = 0; type < NUM_SMX_LightsType; ++type)
|
||||||
{
|
{
|
||||||
for(auto &animation_state: pad_state.animations[panel])
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
animation_state.Update();
|
pad_state.animations[type][panel].Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,6 @@ enum SMX_LightsType
|
|||||||
class SMXPanelAnimation
|
class SMXPanelAnimation
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Return the animation loaded by SMX_LightsAnimation_Load.
|
|
||||||
static SMXPanelAnimation GetLoadedAnimation(int pad, int panel, SMX_LightsType type);
|
|
||||||
|
|
||||||
void Load(const std::vector<SMXGif::SMXGifFrame> &frames, int panel);
|
void Load(const std::vector<SMXGif::SMXGifFrame> &frames, int panel);
|
||||||
|
|
||||||
// The high-level animated GIF frames:
|
// The high-level animated GIF frames:
|
||||||
@ -39,7 +36,8 @@ public:
|
|||||||
// Load an animated GIF as a panel animation. pad is the pad this animation is for (0 or 1),
|
// Load an animated GIF as a panel animation. pad is the pad this animation is for (0 or 1),
|
||||||
// and type is which animation this is for. Any previously loaded animation will be replaced.
|
// and type is which animation this is for. Any previously loaded animation will be replaced.
|
||||||
// On error, false is returned and error is set to a plain-text error message which is valid
|
// On error, false is returned and error is set to a plain-text error message which is valid
|
||||||
// until the next call.
|
// until the next call. On success, the animation can be uploaded to the pad if supported using
|
||||||
|
// SMX_LightsUpload_BeginUpload, or used directly with SMX_LightsAnimation_SetAuto.
|
||||||
SMX_API bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType type, const char **error);
|
SMX_API bool SMX_LightsAnimation_Load(const char *gif, int size, int pad, SMX_LightsType type, const char **error);
|
||||||
|
|
||||||
// Enable or disable automatically handling lights animations. If enabled, any animations
|
// Enable or disable automatically handling lights animations. If enabled, any animations
|
||||||
|
@ -20,12 +20,6 @@ using namespace SMX;
|
|||||||
// Panel animations are sent to the master controller one panel at a time, and
|
// Panel animations are sent to the master controller one panel at a time, and
|
||||||
// each animation can take several commands to upload to fit in the protocol packet
|
// each animation can take several commands to upload to fit in the protocol packet
|
||||||
// size. These commands are stateful.
|
// size. These commands are stateful.
|
||||||
|
|
||||||
// XXX: should be able to upload both pads in parallel
|
|
||||||
// XXX: we can only update all animations in one go, so save the last loaded animations
|
|
||||||
// so the user doesn't have to manually load both to change one of them
|
|
||||||
// do this in SMXConfig, not the SDK
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// Panel names for error messages.
|
// Panel names for error messages.
|
||||||
@ -75,11 +69,6 @@ namespace PanelLightGraphic
|
|||||||
uint8_t delay[64];
|
uint8_t delay[64];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct master_animation_data_t
|
|
||||||
{
|
|
||||||
animation_timing_t animation_timings[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Commands to upload data:
|
// Commands to upload data:
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct upload_packet
|
struct upload_packet
|
||||||
@ -104,6 +93,17 @@ namespace PanelLightGraphic
|
|||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct delay_packet
|
||||||
|
{
|
||||||
|
// 'd' to ask the master to delay.
|
||||||
|
uint8_t cmd = 'd';
|
||||||
|
|
||||||
|
// How long to delay:
|
||||||
|
uint16_t milliseconds = 0;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
// Make sure the packet fits in a command packet.
|
// Make sure the packet fits in a command packet.
|
||||||
static_assert(sizeof(upload_packet) <= 0xFF, "");
|
static_assert(sizeof(upload_packet) <= 0xFF, "");
|
||||||
}
|
}
|
||||||
@ -226,22 +226,12 @@ namespace ProtocolHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the master data. This just has timing information.
|
// Create the master data. This just has timing information.
|
||||||
bool CreateMasterAnimationData(int pad, PanelLightGraphic::master_animation_data_t &master_data, const char **error)
|
bool CreateMasterAnimationData(SMX_LightsType type,
|
||||||
|
const SMXPanelAnimation &animation,
|
||||||
|
PanelLightGraphic::animation_timing_t &animation_timing, const char **error)
|
||||||
{
|
{
|
||||||
// The second animation's graphic indices start where the first one's end.
|
// Released (idle) animations use frames 0-31, and pressed animations use 32-63.
|
||||||
int first_graphic = 0;
|
int first_graphic = type == SMX_LightsType_Released? 0:32;
|
||||||
|
|
||||||
// XXX: It's possible to reuse graphics, which allows us to dedupe animation frames.
|
|
||||||
// We can store 64 total animation graphics shared across the pressed and released
|
|
||||||
// animation, and each animation can have up to 64 timed frames which point into
|
|
||||||
// the graphic list.
|
|
||||||
for(int type = 0; type < NUM_SMX_LightsType; ++type)
|
|
||||||
{
|
|
||||||
// All animations of each type have the same timing for all panels, since
|
|
||||||
// they come from the same GIF, so just look at the first frame.
|
|
||||||
const SMXPanelAnimation &animation = SMXPanelAnimation::GetLoadedAnimation(pad, 0, SMX_LightsType(type));
|
|
||||||
|
|
||||||
PanelLightGraphic::animation_timing_t &animation_timing = master_data.animation_timings[type];
|
|
||||||
|
|
||||||
// Check that we don't have more frames than we can fit in animation_timing.
|
// Check that we don't have more frames than we can fit in animation_timing.
|
||||||
// This is currently the same as the "too many frames" error below, but if
|
// This is currently the same as the "too many frames" error below, but if
|
||||||
@ -255,10 +245,7 @@ namespace ProtocolHelpers
|
|||||||
|
|
||||||
memset(&animation_timing.frames[0], 0xFF, sizeof(animation_timing.frames));
|
memset(&animation_timing.frames[0], 0xFF, sizeof(animation_timing.frames));
|
||||||
for(int i = 0; i < animation.m_aPanelGraphics.size(); ++i)
|
for(int i = 0; i < animation.m_aPanelGraphics.size(); ++i)
|
||||||
{
|
animation_timing.frames[i] = i + first_graphic;
|
||||||
animation_timing.frames[i] = first_graphic;
|
|
||||||
first_graphic++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set frame delays.
|
// Set frame delays.
|
||||||
memset(&animation_timing.delay[0], 0, sizeof(animation_timing.delay));
|
memset(&animation_timing.delay[0], 0, sizeof(animation_timing.delay));
|
||||||
@ -267,24 +254,19 @@ namespace ProtocolHelpers
|
|||||||
animation_timing.delay[i] = delays[i];
|
animation_timing.delay[i] = delays[i];
|
||||||
|
|
||||||
// These frame numbers are relative to the animation, so don't add first_graphic.
|
// These frame numbers are relative to the animation, so don't add first_graphic.
|
||||||
// XXX: frame index, not source frame
|
|
||||||
animation_timing.loop_animation_frame = animation.m_iLoopFrame;
|
animation_timing.loop_animation_frame = animation.m_iLoopFrame;
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack panel graphics.
|
// Pack panel graphics.
|
||||||
bool CreatePanelAnimationData(PanelLightGraphic::panel_animation_data_t &panel_data,
|
bool CreatePanelAnimationData(PanelLightGraphic::panel_animation_data_t &panel_data,
|
||||||
int pad, int panel, const char **error)
|
int pad, SMX_LightsType type, int panel, const SMXPanelAnimation &animation, const char **error)
|
||||||
{
|
{
|
||||||
// We have a single buffer of animation frames for each panel, which we pack
|
// We have a single buffer of animation frames for each panel, which we pack
|
||||||
// both the pressed and released frames into. This is the index of the next
|
// both the pressed and released frames into. This is the index of the next
|
||||||
// frame.
|
// frame.
|
||||||
int next_graphic_idx = 0;
|
int next_graphic_idx = type == SMX_LightsType_Released? 0:32;
|
||||||
|
|
||||||
for(int type = 0; type < NUM_SMX_LightsType; ++type)
|
|
||||||
{
|
|
||||||
const SMXPanelAnimation &animation = SMXPanelAnimation::GetLoadedAnimation(pad, panel, SMX_LightsType(type));
|
|
||||||
|
|
||||||
// Create this animation's 4-bit palette.
|
// Create this animation's 4-bit palette.
|
||||||
if(!ProtocolHelpers::CreatePalette(animation, panel_data.palettes[type]))
|
if(!ProtocolHelpers::CreatePalette(animation, panel_data.palettes[type]))
|
||||||
@ -315,14 +297,13 @@ namespace ProtocolHelpers
|
|||||||
for(int i = 0; i < 3; ++i)
|
for(int i = 0; i < 3; ++i)
|
||||||
color.rgb[i] = uint8_t(color.rgb[i] * 0.6666f);
|
color.rgb[i] = uint8_t(color.rgb[i] * 0.6666f);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create upload packets to upload a block of data.
|
// Create upload packets to upload a block of data.
|
||||||
void CreateUploadPackets(vector<PanelLightGraphic::upload_packet> &packets,
|
void CreateUploadPackets(vector<PanelLightGraphic::upload_packet> &packets,
|
||||||
const void *data_block, int size,
|
const void *data_block, int start, int size,
|
||||||
uint8_t panel, int animation_idx)
|
uint8_t panel, int animation_idx)
|
||||||
{
|
{
|
||||||
const uint8_t *buf = (const uint8_t *) data_block;
|
const uint8_t *buf = (const uint8_t *) data_block;
|
||||||
@ -331,14 +312,15 @@ namespace ProtocolHelpers
|
|||||||
PanelLightGraphic::upload_packet packet;
|
PanelLightGraphic::upload_packet packet;
|
||||||
packet.panel = panel;
|
packet.panel = panel;
|
||||||
packet.animation_idx = animation_idx;
|
packet.animation_idx = animation_idx;
|
||||||
packet.offset = offset;
|
packet.offset = start + offset;
|
||||||
|
|
||||||
int bytes_left = size - offset;
|
int bytes_left = size - offset;
|
||||||
packet.size = min(sizeof(PanelLightGraphic::upload_packet::data), bytes_left);
|
packet.size = min(sizeof(PanelLightGraphic::upload_packet::data), bytes_left);
|
||||||
memcpy(packet.data, buf + offset, packet.size);
|
memcpy(packet.data, buf, packet.size);
|
||||||
packets.push_back(packet);
|
packets.push_back(packet);
|
||||||
|
|
||||||
offset += packet.size;
|
offset += packet.size;
|
||||||
|
buf += packet.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -349,23 +331,16 @@ namespace LightsUploadData
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the loaded graphics for upload.
|
// Prepare the loaded graphics for upload.
|
||||||
bool SMX_LightsUpload_PrepareUpload(int pad, const char **error)
|
bool SMX_LightsUpload_PrepareUpload(int pad, SMX_LightsType type, const SMXPanelAnimation animations[9], const char **error)
|
||||||
{
|
{
|
||||||
// Check that all panel animations are loaded.
|
|
||||||
for(int type = 0; type < NUM_SMX_LightsType; ++type)
|
|
||||||
{
|
|
||||||
const SMXPanelAnimation &animation = SMXPanelAnimation::GetLoadedAnimation(pad, 0, SMX_LightsType(type));
|
|
||||||
if(animation.m_aPanelGraphics.empty())
|
|
||||||
{
|
|
||||||
*error = "Load all panel animations before preparing the upload.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create master animation data.
|
// Create master animation data.
|
||||||
PanelLightGraphic::master_animation_data_t master_data;
|
PanelLightGraphic::animation_timing_t master_animation_data;
|
||||||
memset(&master_data, 0xFF, sizeof(master_data));
|
memset(&master_animation_data, 0xFF, sizeof(master_animation_data));
|
||||||
if(!ProtocolHelpers::CreateMasterAnimationData(pad, master_data, error))
|
|
||||||
|
// All animations of each type have the same timing for all panels, since
|
||||||
|
// they come from the same GIF, so just use the first frame to generate the
|
||||||
|
// master data.
|
||||||
|
if(!ProtocolHelpers::CreateMasterAnimationData(type, animations[0], master_animation_data, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Create panel animation data.
|
// Create panel animation data.
|
||||||
@ -373,49 +348,112 @@ bool SMX_LightsUpload_PrepareUpload(int pad, const char **error)
|
|||||||
memset(&all_panel_data, 0xFF, sizeof(all_panel_data));
|
memset(&all_panel_data, 0xFF, sizeof(all_panel_data));
|
||||||
for(int panel = 0; panel < 9; ++panel)
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
{
|
{
|
||||||
if(!ProtocolHelpers::CreatePanelAnimationData(all_panel_data[panel], pad, panel, error))
|
if(!ProtocolHelpers::CreatePanelAnimationData(all_panel_data[panel], pad, type, panel, animations[panel], error))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We successfully created the data, so there's nothing else that can fail from
|
// We successfully created the data, so there's nothing else that can fail from
|
||||||
// here on.
|
// here on.
|
||||||
|
//
|
||||||
// Create upload packets.
|
// A list of the final commands we'll send:
|
||||||
vector<PanelLightGraphic::upload_packet> packets;
|
|
||||||
for(int type = 0; type < NUM_SMX_LightsType; ++type)
|
|
||||||
{
|
|
||||||
const auto &master_data_block = master_data.animation_timings[type];
|
|
||||||
ProtocolHelpers::CreateUploadPackets(packets, &master_data_block, sizeof(master_data_block), 0xFF, type);
|
|
||||||
|
|
||||||
for(int panel = 0; panel < 9; ++panel)
|
|
||||||
{
|
|
||||||
const auto &panel_data_block = all_panel_data[panel];
|
|
||||||
ProtocolHelpers::CreateUploadPackets(packets, &panel_data_block, sizeof(panel_data_block), panel, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The last packet has the final_packet flag set, to let the master know we're finished.
|
|
||||||
packets.back().final_packet = true;
|
|
||||||
|
|
||||||
// Make a list of strings containing the packets. We don't need the
|
|
||||||
// structs anymore, so this is all we need to keep around.
|
|
||||||
vector<string> &pad_commands = LightsUploadData::commands[pad];
|
vector<string> &pad_commands = LightsUploadData::commands[pad];
|
||||||
pad_commands.clear();
|
pad_commands.clear();
|
||||||
for(const auto &packet: packets)
|
|
||||||
{
|
// Add an upload packet to pad_commands:
|
||||||
|
auto add_packet_command = [&pad_commands](const PanelLightGraphic::upload_packet &packet) {
|
||||||
string command((char *) &packet, sizeof(packet));
|
string command((char *) &packet, sizeof(packet));
|
||||||
pad_commands.push_back(command);
|
pad_commands.push_back(command);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a command to briefly delay the master, to give panels a chance to finish writing to EEPROM.
|
||||||
|
auto add_delay = [&pad_commands](int milliseconds) {
|
||||||
|
PanelLightGraphic::delay_packet packet;
|
||||||
|
packet.milliseconds = milliseconds;
|
||||||
|
|
||||||
|
string command((char *) &packet, sizeof(packet));
|
||||||
|
pad_commands.push_back(command);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the packets we'll send, grouped by panel.
|
||||||
|
vector<PanelLightGraphic::upload_packet> packetsPerPanel[9];
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
{
|
||||||
|
// Only upload the panel graphic data and the palette we're changing. If type
|
||||||
|
// is 0 (SMX_LightsType_Released), we're uploading the first 32 graphics and palette
|
||||||
|
// 0. If it's 1 (SMX_LightsType_Pressed), we're uploading the second 32 graphics
|
||||||
|
// and palette 1.
|
||||||
|
const auto &panel_data_block = all_panel_data[panel];
|
||||||
|
{
|
||||||
|
int first_graphic = type == SMX_LightsType_Released? 0:32;
|
||||||
|
const PanelLightGraphic::graphic_t *graphics = &panel_data_block.graphics[first_graphic];
|
||||||
|
int offset = offsetof(PanelLightGraphic::panel_animation_data_t, graphics[first_graphic]);
|
||||||
|
ProtocolHelpers::CreateUploadPackets(packetsPerPanel[panel], graphics, offset, sizeof(PanelLightGraphic::graphic_t) * 32, panel, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const PanelLightGraphic::palette_t *palette = &panel_data_block.palettes[type];
|
||||||
|
int offset = offsetof(PanelLightGraphic::panel_animation_data_t, palettes[type]);
|
||||||
|
ProtocolHelpers::CreateUploadPackets(packetsPerPanel[panel], palette, offset, sizeof(PanelLightGraphic::palette_t), panel, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It takes 3.4ms per byte to write to EEPROM, and we need to avoid writing data
|
||||||
|
// to any single panel faster than that or data won't be written. However, we're
|
||||||
|
// writing each data separately to each panel, so we can write data to panel 1, then
|
||||||
|
// immediately write to panel 2 while panel 1 is busy doing the write. Taking advantage
|
||||||
|
// of this makes the upload go much faster. Panels will miss commands while they're
|
||||||
|
// writing data, but we don't care if panel 1 misses a command that's writing to panel
|
||||||
|
// 2 that it would ignore anyway.
|
||||||
|
//
|
||||||
|
// We write the first set of packets for each panel, then explicitly delay long enough
|
||||||
|
// for them to finish before writing the next set of packets.
|
||||||
|
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
bool added_any_packets = false;
|
||||||
|
int max_size = 0;
|
||||||
|
for(int panel = 0; panel < 9; ++panel)
|
||||||
|
{
|
||||||
|
// Pull this panel's next packet. It doesn't actually matter what order we
|
||||||
|
// send the packets in.
|
||||||
|
// Add the next packet for each panel.
|
||||||
|
vector<PanelLightGraphic::upload_packet> &packets = packetsPerPanel[panel];
|
||||||
|
if(packets.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PanelLightGraphic::upload_packet packet = packets.back();
|
||||||
|
packets.pop_back();
|
||||||
|
add_packet_command(packet);
|
||||||
|
max_size = max(max_size, packet.size);
|
||||||
|
added_any_packets = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay long enough for the biggest write in this burst to finish. We do this
|
||||||
|
// by sending a command to the master to tell it to delay synchronously by the
|
||||||
|
// right amount.
|
||||||
|
int millisecondsToDelay = lrintf(max_size * 3.4);
|
||||||
|
add_delay(millisecondsToDelay);
|
||||||
|
|
||||||
|
// Stop if there were no more packets to add.
|
||||||
|
if(!added_any_packets)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the master data.
|
||||||
|
vector<PanelLightGraphic::upload_packet> masterPackets;
|
||||||
|
ProtocolHelpers::CreateUploadPackets(masterPackets, &master_animation_data, 0, sizeof(master_animation_data), 0xFF, type);
|
||||||
|
masterPackets.back().final_packet = true;
|
||||||
|
for(const auto &packet: masterPackets)
|
||||||
|
add_packet_command(packet);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start sending a prepared upload.
|
// Start sending a prepared upload.
|
||||||
//
|
//
|
||||||
// The commands to send to upload the data are in pad_commands[pad].
|
// The commands to send to upload the data are in LightsUploadData::commands[pad].
|
||||||
void SMX_LightsUpload_BeginUpload(int pad, SMX_LightsUploadCallback pCallback, void *pUser)
|
void SMX_LightsUpload_BeginUpload(int pad, SMX_LightsUploadCallback pCallback, void *pUser)
|
||||||
{
|
{
|
||||||
// XXX: should we disable panel lights while doing this?
|
|
||||||
shared_ptr<SMXDevice> pDevice = SMXManager::g_pSMX->GetDevice(pad);
|
shared_ptr<SMXDevice> pDevice = SMXManager::g_pSMX->GetDevice(pad);
|
||||||
vector<string> asCommands = LightsUploadData::commands[pad];
|
vector<string> asCommands = LightsUploadData::commands[pad];
|
||||||
int iTotalCommands = asCommands.size();
|
int iTotalCommands = asCommands.size();
|
||||||
@ -430,9 +468,11 @@ void SMX_LightsUpload_BeginUpload(int pad, SMX_LightsUploadCallback pCallback, v
|
|||||||
//
|
//
|
||||||
// If this isn't the last command, make sure progress isn't 100.
|
// If this isn't the last command, make sure progress isn't 100.
|
||||||
// Once we send 100%, the callback is no longer valid.
|
// Once we send 100%, the callback is no longer valid.
|
||||||
int progress = (i*100) / (iTotalCommands-1);
|
int progress;
|
||||||
if(i != iTotalCommands-1)
|
if(i != iTotalCommands-1)
|
||||||
progress = min(progress, 99);
|
progress = min((i*100) / (iTotalCommands - 1), 99);
|
||||||
|
else
|
||||||
|
progress = 100;
|
||||||
|
|
||||||
// We're currently in the SMXManager thread. Call the user thread from
|
// We're currently in the SMXManager thread. Call the user thread from
|
||||||
// the user callback thread.
|
// the user callback thread.
|
||||||
|
@ -9,9 +9,8 @@
|
|||||||
// This is used to upload panel animations to the firmware. This is
|
// This is used to upload panel animations to the firmware. This is
|
||||||
// only needed for offline animations. For live animations, either
|
// only needed for offline animations. For live animations, either
|
||||||
// use SMX_LightsAnimation_SetAuto, or to control lights directly
|
// use SMX_LightsAnimation_SetAuto, or to control lights directly
|
||||||
// (recommended), use SMX_SetLights.
|
// (recommended), use SMX_SetLights. animations[] contains the animations
|
||||||
//
|
// to load.
|
||||||
// Before starting, load animations into SMXPanelAnimation.
|
|
||||||
//
|
//
|
||||||
// Prepare the currently loaded animations to be stored on the pad.
|
// Prepare the currently loaded animations to be stored on the pad.
|
||||||
// Return false with an error message on error.
|
// Return false with an error message on error.
|
||||||
@ -19,11 +18,11 @@
|
|||||||
// All LightTypes must be loaded before beginning the upload.
|
// All LightTypes must be loaded before beginning the upload.
|
||||||
//
|
//
|
||||||
// If a lights upload is already in progress, returns an error.
|
// If a lights upload is already in progress, returns an error.
|
||||||
SMX_API bool SMX_LightsUpload_PrepareUpload(int pad, const char **error);
|
SMX_API bool SMX_LightsUpload_PrepareUpload(int pad, SMX_LightsType type, const SMXPanelAnimation animations[9], const char **error);
|
||||||
|
|
||||||
typedef void SMX_LightsUploadCallback(int progress, void *pUser);
|
typedef void SMX_LightsUploadCallback(int progress, void *pUser);
|
||||||
|
|
||||||
// After a successful call to SMX_LightsUpload_Init, begin uploading data
|
// After a successful call to SMX_LightsUpload_PrepareUpload, begin uploading data
|
||||||
// to the master controller for the given pad and animation type.
|
// to the master controller for the given pad and animation type.
|
||||||
//
|
//
|
||||||
// The callback will be called as the upload progresses, with progress values
|
// The callback will be called as the upload progresses, with progress values
|
||||||
|
@ -58,7 +58,6 @@ namespace smx_config
|
|||||||
// Load animations, and tell the SDK to handle auto-lighting as long as
|
// Load animations, and tell the SDK to handle auto-lighting as long as
|
||||||
// we're running.
|
// we're running.
|
||||||
Helpers.LoadSavedPanelAnimations();
|
Helpers.LoadSavedPanelAnimations();
|
||||||
Helpers.PrepareLoadedAnimations();
|
|
||||||
SMX.SMX.LightsAnimation_SetAuto(true);
|
SMX.SMX.LightsAnimation_SetAuto(true);
|
||||||
|
|
||||||
CreateTrayIcon();
|
CreateTrayIcon();
|
||||||
|
@ -345,28 +345,6 @@ namespace smx_config
|
|||||||
SMX.SMX.LightsAnimation_Load(gif, pad, type, out error);
|
SMX.SMX.LightsAnimation_Load(gif, pad, type, out error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare all loaded animations for upload.
|
|
||||||
//
|
|
||||||
// We do this early, as soon as animations are loaded, so we know whether there are
|
|
||||||
// any errors preventing upload to display in the UI.
|
|
||||||
public static void PrepareLoadedAnimations()
|
|
||||||
{
|
|
||||||
PanelLoadErrors = null;
|
|
||||||
// Prepare animations for both pads.
|
|
||||||
for(int pad = 0; pad < 2; ++pad)
|
|
||||||
{
|
|
||||||
// Store the first error we get. Keep loading pad 1 even if pad 0 has
|
|
||||||
// an error.
|
|
||||||
string error;
|
|
||||||
if(!SMX.SMX.LightsUpload_PrepareUpload(pad, out error) && PanelLoadErrors == null)
|
|
||||||
PanelLoadErrors = error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there was an error preparing animations, we store it here for display. Otherwise,
|
|
||||||
// this is null.
|
|
||||||
public static string PanelLoadErrors = null;
|
|
||||||
|
|
||||||
// Create a .lnk.
|
// Create a .lnk.
|
||||||
public static void CreateShortcut(string outputFile, string targetPath, string arguments)
|
public static void CreateShortcut(string outputFile, string targetPath, string arguments)
|
||||||
{
|
{
|
||||||
|
@ -757,23 +757,6 @@ Use if the platform is too sensitive.</clr:String>
|
|||||||
<TextBlock Name="LeaveRunning" TextAlignment="Center">
|
<TextBlock Name="LeaveRunning" TextAlignment="Center">
|
||||||
Leave this application running to play animations.
|
Leave this application running to play animations.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<StackPanel Name="LeaveRunningOrUpload"
|
|
||||||
HorizontalAlignment="Center" Orientation="Vertical">
|
|
||||||
<TextBlock TextAlignment="Center">
|
|
||||||
Leave this application running to play animations, or upload
|
|
||||||
it to the pad.
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<Button Content="Upload animation to pad" Padding="5,2" Margin="5,10"
|
|
||||||
Click="UploadGIFs" HorizontalAlignment="Center" />
|
|
||||||
</StackPanel>
|
|
||||||
<TextBlock Name="LeaveRunningCantUpload" TextAlignment="Center" Width="400" TextWrapping="Wrap">
|
|
||||||
<TextBlock>This animation is too big to upload it to the pad:</TextBlock>
|
|
||||||
<LineBreak/>
|
|
||||||
<TextBlock Name="UploadErrorReason"></TextBlock>.
|
|
||||||
<LineBreak/>
|
|
||||||
<TextBlock Margin="0,5,0,0">However, you can leaving this application running to use it.</TextBlock>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
@ -242,16 +242,7 @@ namespace smx_config
|
|||||||
SMX.SMXConfig config = activePad.Item2;
|
SMX.SMXConfig config = activePad.Item2;
|
||||||
|
|
||||||
bool uploadsSupported = config.masterVersion >= 4;
|
bool uploadsSupported = config.masterVersion >= 4;
|
||||||
bool uploadPossible = Helpers.PanelLoadErrors == null;
|
|
||||||
|
|
||||||
LeaveRunning.Visibility = uploadsSupported? Visibility.Collapsed:Visibility.Visible;
|
LeaveRunning.Visibility = uploadsSupported? Visibility.Collapsed:Visibility.Visible;
|
||||||
LeaveRunningOrUpload.Visibility = uploadsSupported && uploadPossible? Visibility.Visible:Visibility.Collapsed;
|
|
||||||
LeaveRunningCantUpload.Visibility = uploadsSupported && !uploadPossible? Visibility.Visible:Visibility.Collapsed;
|
|
||||||
|
|
||||||
// If we have an error reason, set it. This is only visible when
|
|
||||||
// we're showing LeaveRunningCantUpload.
|
|
||||||
if(Helpers.PanelLoadErrors != null)
|
|
||||||
UploadErrorReason.Text = Helpers.PanelLoadErrors;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -466,16 +457,28 @@ namespace smx_config
|
|||||||
// Save the GIF to disk so we can load it quickly later.
|
// Save the GIF to disk so we can load it quickly later.
|
||||||
Helpers.SaveAnimationToDisk(pad, type, buf);
|
Helpers.SaveAnimationToDisk(pad, type, buf);
|
||||||
|
|
||||||
// Try to prepare animations for upload. This updates Helpers.PanelLoadErrors.
|
|
||||||
Helpers.PrepareLoadedAnimations();
|
|
||||||
|
|
||||||
// Refresh after loading a GIF to update the "Leave this application running" text.
|
// Refresh after loading a GIF to update the "Leave this application running" text.
|
||||||
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
|
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For firmwares that support it, upload the animation to the pad now. Otherwise,
|
||||||
|
// we'll run the animation directly.
|
||||||
|
foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
|
||||||
|
{
|
||||||
|
int pad = activePad.Item1;
|
||||||
|
|
||||||
|
SMX.SMXConfig config;
|
||||||
|
if(!SMX.SMX.GetConfig(pad, out config))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(config.masterVersion >= 4)
|
||||||
|
UploadLatestGIF();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "Upload animation to pad" button was clicked.
|
private void UploadLatestGIF()
|
||||||
private void UploadGIFs(object sender, RoutedEventArgs e)
|
|
||||||
{
|
{
|
||||||
// Create a progress window. Center it on top of the main window.
|
// Create a progress window. Center it on top of the main window.
|
||||||
ProgressWindow dialog = new ProgressWindow();
|
ProgressWindow dialog = new ProgressWindow();
|
||||||
|
@ -551,34 +551,10 @@ namespace SMX
|
|||||||
|
|
||||||
// SMXPanelAnimationUpload
|
// SMXPanelAnimationUpload
|
||||||
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||||
[return:MarshalAs(UnmanagedType.I1)]
|
|
||||||
private static extern bool SMX_LightsUpload_PrepareUpload(int pad, out IntPtr error);
|
|
||||||
[DllImport("SMX.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
|
||||||
private static extern void SMX_LightsUpload_BeginUpload(int pad,
|
private static extern void SMX_LightsUpload_BeginUpload(int pad,
|
||||||
[MarshalAs(UnmanagedType.FunctionPtr)] InternalLightsUploadCallback callback,
|
[MarshalAs(UnmanagedType.FunctionPtr)] InternalLightsUploadCallback callback,
|
||||||
IntPtr user);
|
IntPtr user);
|
||||||
|
|
||||||
public static bool LightsUpload_PrepareUpload(int pad, out string error)
|
|
||||||
{
|
|
||||||
if(!DLLAvailable())
|
|
||||||
{
|
|
||||||
error = "SMX.DLL not available";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = "";
|
|
||||||
IntPtr error_pointer;
|
|
||||||
bool result = SMX_LightsUpload_PrepareUpload(pad, out error_pointer);
|
|
||||||
if(!result)
|
|
||||||
{
|
|
||||||
// SMX_LightsAnimation_Load takes a char **error, which is set to the error
|
|
||||||
// string.
|
|
||||||
error = Marshal.PtrToStringAnsi(error_pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate void LightsUploadCallback(int progress);
|
public delegate void LightsUploadCallback(int progress);
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user