1717#include " lsdj/SampleCache.hpp"
1818#include " project/ProjectSerialization.hpp"
1919#include " system/SystemTypes.hpp"
20+ #include " util/Base64.hpp"
2021#include " system/sameboy/SameBoyConfig.hpp"
2122#include " system/sameboy/SameBoySystem.hpp"
2223#include " system/sameboy/roles/LsdjKitPatchRole.hpp"
@@ -129,8 +130,13 @@ class LVGLPluginDSP : public Plugin {
129130 {
130131 if (std::strcmp (key, " project" ) != 0 ) return String ();
131132 try {
132- const std::string json = projectConfigToJson (project.snapshotConfig ());
133- return String (json.c_str ());
133+ const auto zip = projectConfigToZip (project.snapshotConfig ());
134+ // DPF state is a NUL-terminated UTF-8 string, so the binary zip
135+ // blob is wrapped in base64. The inner zip is already deflated,
136+ // so the base64 layer's 33% overhead applies to compressed bytes
137+ // — still a large net win over today's JSON+inline-base64.
138+ const std::string encoded = base64::encode (zip);
139+ return String (encoded.c_str ());
134140 } catch (const std::exception& e) {
135141 d_stderr (" [PluginDSP] getState serialization failed: %s" , e.what ());
136142 return String ();
@@ -140,42 +146,36 @@ class LVGLPluginDSP : public Plugin {
140146 void setState (const char * key, const char * value) override
141147 {
142148 if (std::strcmp (key, " project" ) != 0 ) return ;
143- applyProjectFromJson (value);
144- }
145-
146- // Replace the running project with one parsed from a JSON blob. Shared by
147- // DPF setState (host-driven save/restore) and Command::LoadProject (the
148- // user-driven "Load project" menu entry, fed in via the UI thread).
149- // Caller is responsible for the threading context — setState runs DSP-side
150- // before activate; Command::LoadProject runs DSP-side during the run-loop
151- // command drain, same window AddSystem/etc. already mutate the project.
152- void applyProjectFromJson (const char * json)
153- {
154- if (json == nullptr || json[0 ] == ' \0 ' ) return ;
155-
156- std::optional<ProjectConfig> parsed;
157- try {
158- parsed = projectConfigFromJson (std::string_view (json));
159- } catch (const std::exception& e) {
160- d_stderr (" [PluginDSP] applyProjectFromJson parse exception: %s" , e.what ());
161- return ;
162- }
149+ if (value == nullptr || value[0 ] == ' \0 ' ) return ;
150+ const auto decoded = base64::decode (value);
151+ auto parsed = projectConfigFromZip (decoded);
163152 if (!parsed) {
164- d_stderr (" [PluginDSP] applyProjectFromJson : failed to parse project JSON " );
153+ d_stderr (" [PluginDSP] setState : failed to parse project zip " );
165154 return ;
166155 }
156+ applyProjectFromConfig (*parsed);
157+ }
167158
159+ // Replace the running project with a fully-parsed config. Shared by DPF
160+ // setState (host-driven save/restore) and Command::LoadProject (the
161+ // user-driven "Load project" menu entry, fed in via the UI thread which
162+ // does the file IO and zip parse). Caller is responsible for the
163+ // threading context — setState runs DSP-side before activate;
164+ // Command::LoadProject runs DSP-side during the run-loop command drain,
165+ // same window AddSystem/etc. already mutate the project.
166+ void applyProjectFromConfig (const ProjectConfig& parsed)
167+ {
168168 // Tear down current systems. Non-RT (deletes GB instances) — same
169169 // category of work AddSystem/RemoveSystem already do during command
170170 // drain.
171171 project.clearSystems ();
172172 project.config () = ProjectConfig{};
173173
174174 SystemId firstAdded = 0 ;
175- for (const auto & sysConfig : parsed-> systems ) {
175+ for (const auto & sysConfig : parsed. systems ) {
176176 const SystemId id = project.addSystem (sysConfig);
177177 if (id == 0 ) {
178- d_stderr (" [PluginDSP] applyProjectFromJson : addSystem failed for one entry" );
178+ d_stderr (" [PluginDSP] applyProjectFromConfig : addSystem failed for one entry" );
179179 continue ;
180180 }
181181 if (firstAdded == 0 ) firstAdded = id;
@@ -184,7 +184,7 @@ class LVGLPluginDSP : public Plugin {
184184
185185 // Notify the UI (if attached) so it drops its cached project view.
186186 if (!events.tryPush (Event::makeConfigChanged ()))
187- d_stderr (" [PluginDSP] applyProjectFromJson : event queue full; dropping ConfigChanged" );
187+ d_stderr (" [PluginDSP] applyProjectFromConfig : event queue full; dropping ConfigChanged" );
188188 }
189189
190190 // ----------------------------------------------------------------------------------------------------------------
@@ -445,7 +445,7 @@ class LVGLPluginDSP : public Plugin {
445445 kitCfg->kits .push_back (std::move (fresh));
446446 slot = &kitCfg->kits .back ();
447447 }
448- slot->compiledBytes = Base64Bytes ( *owned) ;
448+ slot->compiledBytes = *owned;
449449 slot->compiledHash =
450450 rp::lsdj::SampleCache::hashBytes (owned->data (), owned->size ());
451451 break ;
@@ -466,19 +466,19 @@ class LVGLPluginDSP : public Plugin {
466466 } break ;
467467
468468 case Command::Kind::LoadProject: {
469- std::string* json = cmd.payload .loadProject .json ;
470- if (json ) {
471- applyProjectFromJson (json-> c_str () );
472- // applyProjectFromJson constructs SameBoySystems via
469+ ProjectConfig* config = cmd.payload .loadProject .config ;
470+ if (config ) {
471+ applyProjectFromConfig (*config );
472+ // applyProjectFromConfig constructs SameBoySystems via
473473 // Project::addSystem but doesn't activate them — the
474474 // setState path relies on DPF calling activate() after
475475 // setState, but Command::LoadProject runs mid-run()
476476 // with no following activate. Activate now so the new
477477 // systems actually start emulating; SameBoySystem's
478478 // onActivate is idempotent for already-active systems.
479479 project.onActivate (fSampleRate );
480- delete json ;
481- // applyProjectFromJson already pushes ConfigChanged.
480+ delete config ;
481+ // applyProjectFromConfig already pushes ConfigChanged.
482482 // Don't double-emit by setting projectMutated.
483483 }
484484 } break ;
0 commit comments