Thanks!
this works super fine
although I wasn't able to use the json_fwd.hpp file. But I rearranged the code and as long as I don't change it again, compiling takes much less time.
Really cool! This is an important step for me to actually use BELA live on stage, especially when connecting it to Midi Program Changes. So thanks again for your replies!
Store presets as file
- Edited
giuliomoro so I tried to do something like this but I'm also running into heavy compile times. How do I create this header file without including the json hpp file if I'm using that type for the state private variable and the return type of some functions?
#include "json.hpp"
using json = nlohmann::json;
#ifndef STATE_H_
#define STATE_H_
#endif
class SaveState {
private:
nlohmann::json state;
public:
nlohmann::json getCurrentSong();
void loadSaveState();
SaveState();
};
Should I just store the state as the original string? The getCurrentSong
method could be a private function, but that also would require setting the return type to json
in the header, right?
This is was I was thinking of:
#pragma once
#include <string>
namespace nlohmann {
class json; // forward declaration
};
class SaveState {
private:
nlohmann::json* state; // this is a pointer, so it doesn't need the full class declaration
public:
std::string getCurrentSong();
void loadSaveState();
SaveState();
};
note that state
is now a pointer, so it needs to be assigned a value in the constructor of SaveState:
state = new nlohmann:json;
this way getCurrentSong()
returns a std::string
and a file including this file doesn't need to include (directly or indirectly) json.hpp
.
If you need to return more stuff from getCurrenSong()
than a simple string, you could define your custom struct, e.g.:
struct Song {
std::string name;
std::string author;
unsigned int duration;
}
and use this declaration for getCurrentSong():
Song getCurrentSong();
then, in its implementation you would have to parse the json and fill in the values of the struct object that you'll return.
Every file that includes state.h
will also include json.hpp
which is a 893KB file (!!).
nickcamillo #include "json.hpp"
Instead include json_fwd.hpp
which is a file of only 6KB. This should cut down a lot of compilation time for all files including state.h
.
In state.cpp
you will need to include the full library using #include "json.hpp"
.
@giuliomoro when I try your suggestion I get a reference to json is ambiguous
error when defining the state pointer:
nlohmann::json* state
@Ward when I try to use the fwd declarations I get this error on the definition of the state variable:
implicit instantiation of undefined template 'nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> >, void>' column: 18, line: 12
#include "json_fwd.hpp"
#ifndef STATE_H_
#define STATE_H_
#endif
class SaveState {
private:
nlohmann::json state;
public:
nlohmann::json getCurrentSong();
void loadSaveState();
SaveState();
};};
@Ward these are the files I brought into my bela app: https://github.com/nlohmann/json/tree/develop/single_include/nlohmann
nickcamillo when I try your suggestion I get a reference to json is ambiguous error when defining the state pointer:
nlohmann::json* state
Uff ... turns out nlohmann::json
is not actually a json
class in the nlohmann
namespace, but json
is an alias for basic_json<>
, courtesy of:
using json = basic_json<>;
pfffffff.... so you'd need an even more hack-ish thing:
SaveState.h:
#include <string>
class myjson;
class SaveState {
private:
myjson* state; // this is a pointer, so it doesn't need the full class declaration
public:
std::string getCurrentSong();
void loadSaveState();
SaveState();
};
Then in SaveState.cpp
:
#include "json.hpp"
#include "SaveState.h"
class myjson: public nlohmann::json {}; // definition for the myjson class
Anyhow, given how your library provides a json_fwd.hpp
, things can be simplified without using the myjson
class:
SaveState.h:
#include <string>
#include "json_fwd.hpp"
class SaveState {
private:
nlohmann::json* state; // this is a pointer, so it doesn't need the full class declaration
public:
std::string getCurrentSong();
void loadSaveState();
SaveState();
};
Then in SaveState.cpp:
#include "json.hpp"
....
Note how this solution still uses a json*
pointer instead of a json
object as a member to SaveState
. This will avoid the issue you reported:
nickcamillo when I try to use the fwd declarations I get this error on the definition of the state variable:
implicit instantiation of undefined template 'nlohmann::json_abi_v3_11_2::basic_json<std::map, std::vector, std::__cxx11::basic_string<char>, bool, long long, unsigned long long, double, std::allocator, adl_serializer, std::vector<unsigned char, std::allocator<unsigned char> >, void>' column: 18, line: 12
In general, when using forward declarations you cannot use objects in files that only see the fwd declaration. You can only use them (and/or call any method(including contstructor/destructor) on them) on files that see the full declaration.
- Edited
@giuliomoro Alright, I think I'm on the verge of getting this working. I did what you outline above, the only difference being I kept the return value of getCurrentSong
as nlohmann::json*
because it's an object. Also, currentSongId
is another private int added to the header along with a simple setter.
Everything compiles but then I get a runtime error the first time I try call getCurrentSong
. When I parse the JSON on startup I'm setting the private state variable like so:
void SaveState::loadSaveState() {
std::ifstream f(kStateJsonFilepath);
nlohmann::json tempState = json::parse(f);
state = &tempState;
int currentSongId = state->value(kCurrentSongId, kIdNotFound);
rt_printf("CURRENT SONG ID: %d\n", currentSongId);
if (currentSongId != kIdNotFound) {
setCurrentSongId(currentSongId);
}
rt_printf("state: %s\n", state->dump(-1).c_str());
}
At the end of that same function, I've verified that the json has been properly parsed via:
rt_printf("state: %s\n", state->dump(-1).c_str());
which looks exactly as I'd expect.
However, when I call getCurrentSong
and try to run the same print statement from there, I'm getting mixed bad results:
nlohmann::json* SaveState::getCurrentSong() {
rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
for(auto &song : (*state)[kSongs]) {
int songId = song[kSongId];
if (songId == currentSongId) {
return &song;
}
}
return &kIdNotFound;
}
The first time it's called I get
rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
// null
The second time I get
rt_printf("state in getCurrentSong: %s\n", state->dump(-1).c_str());
// {"songs": null}
There are no places in which that private state
variable are being set or modified except for in that initial load function where I am successfully setting/reading from/printing out the dereferenced variable. The getCurrentSong
function works perfectly when the state
variable is not a pointer. The only change I made to the loadSaveState
function was to first load the parsed JSON data into a tempState
variable before assigning the class instance's state
variable to the pointer. I only did this because trying to do it directly like so
state = &(json::parse(f));
produced an error: `Taking the address of a temporary object of type 'nlohmann::json...etc'
Am I doing anything obviously wrong here? It seems to me like maybe that tempState
variable is getting discarded at the end of the function call and so when I try to read the state variable later it is null? Although I don't see why I'd get two different results when trying to parse the state. I'm not sure I can see the way around this without declaring another private variable in the header that is not a pointer. Thoughts?
nickcamillo nlohmann::json tempState = json::parse(f);
state = &tempState;
When tempState
goes out of scope- at the end of the function- the pointer stored in state
becomes invalid and should no longer be used.
nickcamillo There are no places in which that private state variable are being set or modified except for in that initial load function where I am successfully setting/reading from/printing out the dereferenced variable.
The pointer doesn't change, but the content of the memory it points to may, once the object that was originally stored there gets destroyed.
giuliomoro so does that mean I woud need to do i/o and re-parse the file every time I need to access the current state? or does this mean that instead of leaving it as the nlohmann::json type I should parse, then assign to a private struct that I define myself that will remain in memory?
- Edited
you need to make a copy of the memory instead of the pointer to the memory, which means you need to allocate memory for it. I think something like this may work
delete tempState; // delete old object
state = new json(json::parse(f));
you'll also need to set in the constructor or in the class declaration and to call tempState = nullptr
delete state
in the destructor. The former is important so the first call to delete
is successful (it's valid to call delete
on a nullptr
); the latter is important so that the json
object is properly destroyed and its memory released when the SaveState
object is destroyed.
giuliomoro wouldn't this require typing tempState as nlohmann::json in the header?
Not sure I understand why it would... Does the change I suggested work?
giuliomoro I don't understand how the constructor would know about the tempState
variable without declaring it in the header. Using state = new json(json::parse(f))
seems to work without the tempState
variable.
oh I see, my bad. It should be:
delete state; // delete old object
state = new json(json::parse(f));
you'll also need to set state = nullptr
in the constructor or in the class declaration.