mirror of https://github.com/bsnes-emu/bsnes.git
Update to v100r16 release.
byuu says: (Windows users may need to include <sys/time.h> at the top of nall/chrono.hpp, not sure.) Unchangelog: - forgot to add the Scheduler clock=0 fix because I have the memory of a goldfish Changelog: - new icarus database with nine additional games - hiro(GTK,Qt) won't constantly write its settings.bml file to disk anymore - added latency simulator for fun (settings.bml => Input/Latency in milliseconds) So the last one ... I wanted to test out nall::chrono, and I was also thinking that by polling every emulated frame, it's pretty wasteful when you are using Fast Forward and hitting 200+fps. As I've said before, calls to ruby::input::poll are not cheap. So to get around this, I added a limiter so that if you called the hardware poll function within N milliseconds, it'll return without doing any actual work. And indeed, that increases my framerate of Zelda 3 uncapped from 133fps to 142fps. Yay. But it's not a "real" speedup, as it only helps you when you exceed 100% speed (theoretically, you'd need to crack 300% speed since the game itself will poll at 16ms at 100% speed, but yet it sped up Zelda 3, so who am I to complain?) I threw the latency value into the settings file. It should be 16, but I set it to 5 since that was the lowest before it started negatively impacting uncapped speeds. You're wasting your time and CPU cycles setting it lower than 5, but if people like placebo effects it might work. Maybe I should let it be a signed integer so people can set it to -16 and think it's actually faster :P (I'm only joking. I took out the 96000hz audio placebo effect as well. Not really into psychological tricks anymore.) But yeah seriously, I didn't do this to start this discussion again for the billionth time. Please don't go there. And please don't tell me this WIP has higher/lower latency than before. I don't want to hear it. The only reason I bring it up is for the fun part that is worth discussing: put up or shut up time on how sensitive you are to latency! You can set the value above 5 to see how games feel. I personally can't really tell a difference until about 50. And I can't be 100% confident it's worse until about 75. But ... when I set it to 150, games become "extra difficult" ... the higher it goes, the worse it gets :D For this WIP, I've left no upper limit cap. I'll probably set a cap of something like 500ms or 1000ms for the official release. Need to balance user error/trolling with enjoyability. I'll think about it. [...] Now, what I worry about is stupid people seeing it and thinking it's an "added latency" setting, as if anyone would intentionally make things worse by default. This is a limiter. So if 5ms have passed since the game last polled, and that will be the case 99.9% of the time in games, the next poll will happen just in time, immediately when the game polls the inputs. Thus, a value below 1/<framerate>ms is not only pointless, if you go too low it will ruin your fast forward max speeds. I did say I didn't want to resort to placebo tricks, but I also don't want to spark up public discussion on this again either. So it might be best to default Input/Latency to 0ms, and internally have a max(5, latency) wrapper around the value.
This commit is contained in:
parent
c50723ef61
commit
f5e5bf1772
|
@ -11,7 +11,7 @@ using namespace nall;
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const string Name = "higan";
|
static const string Name = "higan";
|
||||||
static const string Version = "100.15";
|
static const string Version = "100.16";
|
||||||
static const string Author = "byuu";
|
static const string Author = "byuu";
|
||||||
static const string License = "GPLv3";
|
static const string License = "GPLv3";
|
||||||
static const string Website = "http://byuu.org/";
|
static const string Website = "http://byuu.org/";
|
||||||
|
|
|
@ -43,6 +43,7 @@ Settings::Settings() {
|
||||||
set("Audio/Resampler", "Sinc");
|
set("Audio/Resampler", "Sinc");
|
||||||
|
|
||||||
set("Input/Driver", ruby::Input::optimalDriver());
|
set("Input/Driver", ruby::Input::optimalDriver());
|
||||||
|
set("Input/Latency", 5);
|
||||||
set("Input/FocusLoss/Pause", false);
|
set("Input/FocusLoss/Pause", false);
|
||||||
set("Input/FocusLoss/AllowInput", false);
|
set("Input/FocusLoss/AllowInput", false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,8 @@ auto InputManager::appendHotkeys() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputManager::pollHotkeys() -> void {
|
auto InputManager::pollHotkeys() -> void {
|
||||||
|
if(!presentation || !presentation->focused()) return;
|
||||||
|
|
||||||
for(auto& hotkey : hotkeys) {
|
for(auto& hotkey : hotkeys) {
|
||||||
int16 state = hotkey->poll();
|
int16 state = hotkey->poll();
|
||||||
if(hotkey->state == 0 && state == 1 && hotkey->press) hotkey->press();
|
if(hotkey->state == 0 && state == 1 && hotkey->press) hotkey->press();
|
||||||
|
|
|
@ -144,6 +144,7 @@ auto InputMapping::deviceName() -> string {
|
||||||
|
|
||||||
InputManager::InputManager() {
|
InputManager::InputManager() {
|
||||||
inputManager = this;
|
inputManager = this;
|
||||||
|
latency = max(1u, settings["Input/Latency"].natural());
|
||||||
|
|
||||||
for(auto& emulator : program->emulators) {
|
for(auto& emulator : program->emulators) {
|
||||||
auto& inputEmulator = emulators(emulators.size());
|
auto& inputEmulator = emulators(emulators.size());
|
||||||
|
@ -201,6 +202,11 @@ auto InputManager::bind() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputManager::poll() -> void {
|
auto InputManager::poll() -> void {
|
||||||
|
//polling actual hardware is very time-consuming: skip call if poll was called too recently
|
||||||
|
auto thisPoll = chrono::millisecond();
|
||||||
|
if(thisPoll - lastPoll < latency) return;
|
||||||
|
lastPoll = thisPoll;
|
||||||
|
|
||||||
auto devices = input->poll();
|
auto devices = input->poll();
|
||||||
bool changed = devices.size() != this->devices.size();
|
bool changed = devices.size() != this->devices.size();
|
||||||
if(!changed) {
|
if(!changed) {
|
||||||
|
@ -213,8 +219,6 @@ auto InputManager::poll() -> void {
|
||||||
this->devices = devices;
|
this->devices = devices;
|
||||||
bind();
|
bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(presentation && presentation->focused()) pollHotkeys();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InputManager::onChange(shared_pointer<HID::Device> device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void {
|
auto InputManager::onChange(shared_pointer<HID::Device> device, uint group, uint input, int16_t oldValue, int16_t newValue) -> void {
|
||||||
|
|
|
@ -64,6 +64,8 @@ struct InputManager {
|
||||||
vector<InputHotkey*> hotkeys;
|
vector<InputHotkey*> hotkeys;
|
||||||
|
|
||||||
InputEmulator* emulator = nullptr; //points to InputEmulator that represents the currently active emulator
|
InputEmulator* emulator = nullptr; //points to InputEmulator that represents the currently active emulator
|
||||||
|
uint64 lastPoll; //time in milliseconds since last call to poll()
|
||||||
|
uint64 latency; //minimum time in milliseconds before poll() can be called again
|
||||||
};
|
};
|
||||||
|
|
||||||
extern unique_pointer<InputManager> inputManager;
|
extern unique_pointer<InputManager> inputManager;
|
||||||
|
|
|
@ -14,7 +14,7 @@ AboutWindow::AboutWindow() {
|
||||||
"Website:"
|
"Website:"
|
||||||
});
|
});
|
||||||
informationRight.setFont(Font().setBold()).setAlignment(0.0).setText({
|
informationRight.setFont(Font().setBold()).setAlignment(0.0).setText({
|
||||||
string{Emulator::Version}.replace("v", ""), "\n",
|
Emulator::Version, "\n",
|
||||||
Emulator::Author, "\n",
|
Emulator::Author, "\n",
|
||||||
Emulator::License, "\n",
|
Emulator::License, "\n",
|
||||||
Emulator::Website
|
Emulator::Website
|
||||||
|
|
|
@ -72,10 +72,10 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint frameCounter = 0;
|
static uint frameCounter = 0;
|
||||||
static time_t previous, current;
|
static uint64 previous, current;
|
||||||
frameCounter++;
|
frameCounter++;
|
||||||
|
|
||||||
time(¤t);
|
current = chrono::timestamp();
|
||||||
if(current != previous) {
|
if(current != previous) {
|
||||||
previous = current;
|
previous = current;
|
||||||
statusText = {"FPS: ", frameCounter};
|
statusText = {"FPS: ", frameCounter};
|
||||||
|
@ -91,6 +91,7 @@ auto Program::audioSample(const double* samples, uint channels) -> void {
|
||||||
|
|
||||||
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
|
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
|
||||||
if(presentation->focused() || settings["Input/FocusLoss/AllowInput"].boolean()) {
|
if(presentation->focused() || settings["Input/FocusLoss/AllowInput"].boolean()) {
|
||||||
|
inputManager->poll();
|
||||||
auto& mapping = inputManager->emulator->ports[port].devices[device].mappings[input];
|
auto& mapping = inputManager->emulator->ports[port].devices[device].mappings[input];
|
||||||
return mapping.poll();
|
return mapping.poll();
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ Program::Program(string_vector args) {
|
||||||
auto Program::main() -> void {
|
auto Program::main() -> void {
|
||||||
updateStatusText();
|
updateStatusText();
|
||||||
inputManager->poll();
|
inputManager->poll();
|
||||||
|
inputManager->pollHotkeys();
|
||||||
|
|
||||||
if(!emulator || !emulator->loaded() || pause || (!presentation->focused() && settings["Input/FocusLoss/Pause"].boolean())) {
|
if(!emulator || !emulator->loaded() || pause || (!presentation->focused() && settings["Input/FocusLoss/Pause"].boolean())) {
|
||||||
audio->clear();
|
audio->clear();
|
||||||
|
|
|
@ -2,7 +2,7 @@ auto Program::stateName(uint slot, bool manager) -> string {
|
||||||
return {
|
return {
|
||||||
mediumPaths(1), "higan/states/",
|
mediumPaths(1), "higan/states/",
|
||||||
manager ? "managed/" : "quick/",
|
manager ? "managed/" : "quick/",
|
||||||
"slot-", numeral(slot, 2L), ".bst"
|
"slot-", slot, ".bst"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include <nall/platform.hpp>
|
#include <nall/platform.hpp>
|
||||||
#include <nall/config.hpp>
|
|
||||||
#include <nall/directory.hpp>
|
#include <nall/directory.hpp>
|
||||||
#include <nall/function.hpp>
|
#include <nall/function.hpp>
|
||||||
#include <nall/image.hpp>
|
#include <nall/image.hpp>
|
||||||
|
|
|
@ -42,9 +42,6 @@ auto pApplication::initialize() -> void {
|
||||||
display = XOpenDisplay(nullptr);
|
display = XOpenDisplay(nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
settings = new Settings;
|
|
||||||
settings->load();
|
|
||||||
|
|
||||||
//set WM_CLASS to Application::name()
|
//set WM_CLASS to Application::name()
|
||||||
if(Application::state.name) gdk_set_program_class(Application::state.name);
|
if(Application::state.name) gdk_set_program_class(Application::state.name);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ auto pKeyboard::poll() -> vector<bool> {
|
||||||
#if defined(DISPLAY_XORG)
|
#if defined(DISPLAY_XORG)
|
||||||
XQueryKeymap(pApplication::display, state);
|
XQueryKeymap(pApplication::display, state);
|
||||||
#endif
|
#endif
|
||||||
for(auto& code : settings->keycodes) {
|
for(auto& code : settings.keycodes) {
|
||||||
result.append(_pressed(state, code));
|
result.append(_pressed(state, code));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -227,7 +227,7 @@ auto pKeyboard::initialize() -> void {
|
||||||
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0;
|
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0;
|
||||||
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0;
|
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0;
|
||||||
#endif
|
#endif
|
||||||
settings->keycodes.append(lo | (hi << 8));
|
settings.keycodes.append(lo | (hi << 8));
|
||||||
};
|
};
|
||||||
|
|
||||||
#define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; }
|
#define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; }
|
||||||
|
|
|
@ -1,26 +1,38 @@
|
||||||
namespace hiro {
|
namespace hiro {
|
||||||
|
|
||||||
Settings::Settings() {
|
Settings::Settings() {
|
||||||
geometry.append(geometry.frameX = 4, "FrameX");
|
string path = {Path::local(), "hiro/"};
|
||||||
geometry.append(geometry.frameY = 24, "FrameY");
|
auto document = BML::unserialize(file::read({path, "gtk.bml"}));
|
||||||
geometry.append(geometry.frameWidth = 8, "FrameWidth");
|
|
||||||
geometry.append(geometry.frameHeight = 28, "FrameHeight");
|
auto get = [&](string_view name) {
|
||||||
geometry.append(geometry.menuHeight = 20, "MenuHeight");
|
return document[name];
|
||||||
geometry.append(geometry.statusHeight = 20, "StatusHeight");
|
};
|
||||||
append(geometry, "Geometry");
|
|
||||||
window.append(window.backgroundColor = 0xedeceb, "BackgroundColor");
|
geometry.frameX = get("Geometry/FrameX").integer();
|
||||||
append(window, "Window");
|
geometry.frameY = get("Geometry/FrameY").integer();
|
||||||
|
geometry.frameWidth = get("Geometry/FrameWidth").integer();
|
||||||
|
geometry.frameHeight = get("Geometry/FrameHeight").integer();
|
||||||
|
geometry.menuHeight = get("Geometry/MenuHeight").integer();
|
||||||
|
geometry.statusHeight = get("Geometry/StatusHeight").integer();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Settings::load() -> void {
|
Settings::~Settings() {
|
||||||
string path = {Path::config(), "hiro/"};
|
string path = {Path::local(), "hiro/"};
|
||||||
Configuration::Document::load({path, "gtk.bml"});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Settings::save() -> void {
|
|
||||||
string path = {Path::config(), "hiro/"};
|
|
||||||
directory::create(path, 0755);
|
directory::create(path, 0755);
|
||||||
Configuration::Document::save({path, "gtk.bml"});
|
|
||||||
|
Markup::Node document;
|
||||||
|
auto set = [&](string_view name, string_view value) {
|
||||||
|
document(name).setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
set("Geometry/FrameX", geometry.frameX);
|
||||||
|
set("Geometry/FrameY", geometry.frameY);
|
||||||
|
set("Geometry/FrameWidth", geometry.frameWidth);
|
||||||
|
set("Geometry/FrameHeight", geometry.frameHeight);
|
||||||
|
set("Geometry/MenuHeight", geometry.menuHeight);
|
||||||
|
set("Geometry/StatusHeight", geometry.statusHeight);
|
||||||
|
|
||||||
|
file::write({path, "gtk.bml"}, BML::serialize(document));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
namespace hiro {
|
namespace hiro {
|
||||||
|
|
||||||
struct Settings : Configuration::Document {
|
struct Settings {
|
||||||
|
Settings();
|
||||||
|
~Settings();
|
||||||
|
|
||||||
vector<uint16_t> keycodes;
|
vector<uint16_t> keycodes;
|
||||||
|
|
||||||
struct Geometry : Configuration::Node {
|
struct Geometry {
|
||||||
signed frameX;
|
int frameX = 4;
|
||||||
signed frameY;
|
int frameY = 24;
|
||||||
signed frameWidth;
|
int frameWidth = 8;
|
||||||
signed frameHeight;
|
int frameHeight = 28;
|
||||||
signed menuHeight;
|
int menuHeight = 20;
|
||||||
signed statusHeight;
|
int statusHeight = 20;
|
||||||
} geometry;
|
} geometry;
|
||||||
|
|
||||||
struct Window : Configuration::Node {
|
|
||||||
unsigned backgroundColor;
|
|
||||||
} window;
|
|
||||||
|
|
||||||
Settings();
|
|
||||||
auto load() -> void;
|
|
||||||
auto save() -> void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static Settings* settings = nullptr;
|
static Settings settings;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,11 +48,10 @@ static auto Window_configure(GtkWidget* widget, GdkEvent* event, pWindow* p) ->
|
||||||
|
|
||||||
if(!p->state().fullScreen) {
|
if(!p->state().fullScreen) {
|
||||||
//update geometry settings
|
//update geometry settings
|
||||||
settings->geometry.frameX = client.x - border.x;
|
settings.geometry.frameX = client.x - border.x;
|
||||||
settings->geometry.frameY = client.y - border.y;
|
settings.geometry.frameY = client.y - border.y;
|
||||||
settings->geometry.frameWidth = border.width - client.width;
|
settings.geometry.frameWidth = border.width - client.width;
|
||||||
settings->geometry.frameHeight = border.height - client.height;
|
settings.geometry.frameHeight = border.height - client.height;
|
||||||
settings->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Geometry geometry = {
|
Geometry geometry = {
|
||||||
|
@ -212,10 +211,10 @@ auto pWindow::frameMargin() const -> Geometry {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
settings->geometry.frameX,
|
settings.geometry.frameX,
|
||||||
settings->geometry.frameY + _menuHeight(),
|
settings.geometry.frameY + _menuHeight(),
|
||||||
settings->geometry.frameWidth,
|
settings.geometry.frameWidth,
|
||||||
settings->geometry.frameHeight + _menuHeight() + _statusHeight()
|
settings.geometry.frameHeight + _menuHeight() + _statusHeight()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,13 +327,13 @@ auto pWindow::setVisible(bool visible) -> void {
|
||||||
if(gtk_widget_get_visible(gtkMenu)) {
|
if(gtk_widget_get_visible(gtkMenu)) {
|
||||||
GtkAllocation allocation;
|
GtkAllocation allocation;
|
||||||
gtk_widget_get_allocation(gtkMenu, &allocation);
|
gtk_widget_get_allocation(gtkMenu, &allocation);
|
||||||
settings->geometry.menuHeight = allocation.height;
|
settings.geometry.menuHeight = allocation.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(gtk_widget_get_visible(gtkStatus)) {
|
if(gtk_widget_get_visible(gtkStatus)) {
|
||||||
GtkAllocation allocation;
|
GtkAllocation allocation;
|
||||||
gtk_widget_get_allocation(gtkStatus, &allocation);
|
gtk_widget_get_allocation(gtkStatus, &allocation);
|
||||||
settings->geometry.statusHeight = allocation.height;
|
settings.geometry.statusHeight = allocation.height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,7 +357,7 @@ auto pWindow::_append(mMenu& menu) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::_menuHeight() const -> signed {
|
auto pWindow::_menuHeight() const -> signed {
|
||||||
return gtk_widget_get_visible(gtkMenu) ? settings->geometry.menuHeight : 0;
|
return gtk_widget_get_visible(gtkMenu) ? settings.geometry.menuHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::_setIcon(const string& pathname) -> bool {
|
auto pWindow::_setIcon(const string& pathname) -> bool {
|
||||||
|
@ -414,7 +413,7 @@ auto pWindow::_setStatusVisible(bool visible) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::_statusHeight() const -> signed {
|
auto pWindow::_statusHeight() const -> signed {
|
||||||
return gtk_widget_get_visible(gtkStatus) ? settings->geometry.statusHeight : 0;
|
return gtk_widget_get_visible(gtkStatus) ? settings.geometry.statusHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,6 @@ auto pApplication::syncX() -> void {
|
||||||
auto pApplication::initialize() -> void {
|
auto pApplication::initialize() -> void {
|
||||||
display = XOpenDisplay(0);
|
display = XOpenDisplay(0);
|
||||||
|
|
||||||
settings = new Settings;
|
|
||||||
settings->load();
|
|
||||||
|
|
||||||
static int argc = 1;
|
static int argc = 1;
|
||||||
static char* argv[] = {new char[8], nullptr};
|
static char* argv[] = {new char[8], nullptr};
|
||||||
strcpy(argv[0], "hiro");
|
strcpy(argv[0], "hiro");
|
||||||
|
|
|
@ -6,7 +6,7 @@ auto pKeyboard::poll() -> vector<bool> {
|
||||||
vector<bool> result;
|
vector<bool> result;
|
||||||
char state[256];
|
char state[256];
|
||||||
XQueryKeymap(pApplication::display, state);
|
XQueryKeymap(pApplication::display, state);
|
||||||
for(auto& code : settings->keycodes) {
|
for(auto& code : settings.keycodes) {
|
||||||
result.append(_pressed(state, code));
|
result.append(_pressed(state, code));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -32,7 +32,7 @@ auto pKeyboard::initialize() -> void {
|
||||||
auto append = [](unsigned lo, unsigned hi = 0) {
|
auto append = [](unsigned lo, unsigned hi = 0) {
|
||||||
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0;
|
lo = lo ? (uint8_t)XKeysymToKeycode(pApplication::display, lo) : 0;
|
||||||
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0;
|
hi = hi ? (uint8_t)XKeysymToKeycode(pApplication::display, hi) : 0;
|
||||||
settings->keycodes.append(lo << 0 | hi << 8);
|
settings.keycodes.append(lo << 0 | hi << 8);
|
||||||
};
|
};
|
||||||
|
|
||||||
#define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; }
|
#define map(name, ...) if(key == name) { append(__VA_ARGS__); continue; }
|
||||||
|
|
|
@ -1,26 +1,38 @@
|
||||||
namespace hiro {
|
namespace hiro {
|
||||||
|
|
||||||
static Settings* settings = nullptr;
|
|
||||||
|
|
||||||
Settings::Settings() {
|
Settings::Settings() {
|
||||||
geometry.append(geometry.frameX = 4, "FrameX");
|
string path = {Path::local(), "hiro/"};
|
||||||
geometry.append(geometry.frameY = 24, "FrameY");
|
auto document = BML::unserialize(file::read({path, "qt.bml"}));
|
||||||
geometry.append(geometry.frameWidth = 8, "FrameWidth");
|
|
||||||
geometry.append(geometry.frameHeight = 28, "FrameHeight");
|
auto get = [&](string_view name) {
|
||||||
geometry.append(geometry.menuHeight = 20, "MenuHeight");
|
return document[name];
|
||||||
geometry.append(geometry.statusHeight = 20, "StatusHeight");
|
};
|
||||||
append(geometry, "Geometry");
|
|
||||||
|
geometry.frameX = get("Geometry/FrameX").integer();
|
||||||
|
geometry.frameY = get("Geometry/FrameY").integer();
|
||||||
|
geometry.frameWidth = get("Geometry/FrameWidth").integer();
|
||||||
|
geometry.frameHeight = get("Geometry/FrameHeight").integer();
|
||||||
|
geometry.menuHeight = get("Geometry/MenuHeight").integer();
|
||||||
|
geometry.statusHeight = get("Geometry/StatusHeight").integer();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Settings::load() -> void {
|
Settings::~Settings() {
|
||||||
string path{Path::config(), "hiro/"};
|
string path = {Path::local(), "hiro/"};
|
||||||
Configuration::Document::load({path, "qt.bml"});
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Settings::save() -> void {
|
|
||||||
string path{Path::config(), "hiro/"};
|
|
||||||
directory::create(path, 0755);
|
directory::create(path, 0755);
|
||||||
Configuration::Document::save({path, "qt.bml"});
|
|
||||||
|
Markup::Node document;
|
||||||
|
auto set = [&](string_view name, string_view value) {
|
||||||
|
document(name).setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
set("Geometry/FrameX", geometry.frameX);
|
||||||
|
set("Geometry/FrameY", geometry.frameY);
|
||||||
|
set("Geometry/FrameWidth", geometry.frameWidth);
|
||||||
|
set("Geometry/FrameHeight", geometry.frameHeight);
|
||||||
|
set("Geometry/MenuHeight", geometry.menuHeight);
|
||||||
|
set("Geometry/StatusHeight", geometry.statusHeight);
|
||||||
|
|
||||||
|
file::write({path, "qt.bml"}, BML::serialize(document));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
namespace hiro {
|
namespace hiro {
|
||||||
|
|
||||||
struct Settings : Configuration::Document {
|
struct Settings {
|
||||||
|
Settings();
|
||||||
|
~Settings();
|
||||||
|
|
||||||
vector<uint16_t> keycodes;
|
vector<uint16_t> keycodes;
|
||||||
|
|
||||||
struct Geometry : Configuration::Node {
|
struct Geometry {
|
||||||
signed frameX;
|
int frameX = 4;
|
||||||
signed frameY;
|
int frameY = 24;
|
||||||
signed frameWidth;
|
int frameWidth = 8;
|
||||||
signed frameHeight;
|
int frameHeight = 28;
|
||||||
signed menuHeight;
|
int menuHeight = 20;
|
||||||
signed statusHeight;
|
int statusHeight = 20;
|
||||||
} geometry;
|
} geometry;
|
||||||
|
|
||||||
Settings();
|
|
||||||
auto load() -> void;
|
|
||||||
auto save() -> void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static Settings settings;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,10 @@ auto pWindow::frameMargin() const -> Geometry {
|
||||||
0, _menuHeight() + _statusHeight()
|
0, _menuHeight() + _statusHeight()
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
settings->geometry.frameX,
|
settings.geometry.frameX,
|
||||||
settings->geometry.frameY + _menuHeight(),
|
settings.geometry.frameY + _menuHeight(),
|
||||||
settings->geometry.frameWidth,
|
settings.geometry.frameWidth,
|
||||||
settings->geometry.frameHeight + _menuHeight() + _statusHeight()
|
settings.geometry.frameHeight + _menuHeight() + _statusHeight()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,11 +215,11 @@ auto pWindow::_append(mWidget& widget) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::_menuHeight() const -> signed {
|
auto pWindow::_menuHeight() const -> signed {
|
||||||
return qtMenuBar->isVisible() ? settings->geometry.menuHeight : 0;
|
return qtMenuBar->isVisible() ? settings.geometry.menuHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::_statusHeight() const -> signed {
|
auto pWindow::_statusHeight() const -> signed {
|
||||||
return qtStatusBar->isVisible() ? settings->geometry.statusHeight : 0;
|
return qtStatusBar->isVisible() ? settings.geometry.statusHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pWindow::_updateFrameGeometry() -> void {
|
auto pWindow::_updateFrameGeometry() -> void {
|
||||||
|
@ -227,22 +227,20 @@ auto pWindow::_updateFrameGeometry() -> void {
|
||||||
QRect border = qtWindow->frameGeometry();
|
QRect border = qtWindow->frameGeometry();
|
||||||
QRect client = qtWindow->geometry();
|
QRect client = qtWindow->geometry();
|
||||||
|
|
||||||
settings->geometry.frameX = client.x() - border.x();
|
settings.geometry.frameX = client.x() - border.x();
|
||||||
settings->geometry.frameY = client.y() - border.y();
|
settings.geometry.frameY = client.y() - border.y();
|
||||||
settings->geometry.frameWidth = border.width() - client.width();
|
settings.geometry.frameWidth = border.width() - client.width();
|
||||||
settings->geometry.frameHeight = border.height() - client.height();
|
settings.geometry.frameHeight = border.height() - client.height();
|
||||||
|
|
||||||
if(qtMenuBar->isVisible()) {
|
if(qtMenuBar->isVisible()) {
|
||||||
pApplication::syncX();
|
pApplication::syncX();
|
||||||
settings->geometry.menuHeight = qtMenuBar->height();
|
settings.geometry.menuHeight = qtMenuBar->height();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(qtStatusBar->isVisible()) {
|
if(qtStatusBar->isVisible()) {
|
||||||
pApplication::syncX();
|
pApplication::syncX();
|
||||||
settings->geometry.statusHeight = qtStatusBar->height();
|
settings.geometry.statusHeight = qtStatusBar->height();
|
||||||
}
|
}
|
||||||
|
|
||||||
settings->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto QtWindow::closeEvent(QCloseEvent* event) -> void {
|
auto QtWindow::closeEvent(QCloseEvent* event) -> void {
|
||||||
|
@ -305,8 +303,8 @@ auto QtWindow::resizeEvent(QResizeEvent*) -> void {
|
||||||
auto QtWindow::sizeHint() const -> QSize {
|
auto QtWindow::sizeHint() const -> QSize {
|
||||||
unsigned width = p.state().geometry.width();
|
unsigned width = p.state().geometry.width();
|
||||||
unsigned height = p.state().geometry.height();
|
unsigned height = p.state().geometry.height();
|
||||||
if(p.qtMenuBar->isVisible()) height += settings->geometry.menuHeight;
|
if(p.qtMenuBar->isVisible()) height += settings.geometry.menuHeight;
|
||||||
if(p.qtStatusBar->isVisible()) height += settings->geometry.statusHeight;
|
if(p.qtStatusBar->isVisible()) height += settings.geometry.statusHeight;
|
||||||
return QSize(width, height);
|
return QSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,79 @@ cartridge sha256:e678a29a93111cf2016c487ba9977b14de8f719040651a42c74bd74eea2d0d8
|
||||||
: name: Death and Return of Superman, The
|
: name: Death and Return of Superman, The
|
||||||
: title: The Death and Return of Superman
|
: title: The Death and Return of Superman
|
||||||
|
|
||||||
|
cartridge sha256:039beb46f81ad9e0844ecec420cc78bfdbf2b1ae940adb4fdf08dbf1b55ac7ed
|
||||||
|
:board region=pal
|
||||||
|
: rom name=program.rom size=0x200000
|
||||||
|
: map address=00-3f,80-bf:8000-ffff
|
||||||
|
: map address=40-7d,c0-ff:0000-ffff
|
||||||
|
: ram name=save.ram size=0x2000
|
||||||
|
: map address=20-3f,a0-bf:6000-7fff mask=0xe000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNSP-JG-NOE
|
||||||
|
: board: SHVC-1J3M-20
|
||||||
|
: revision: 1.1
|
||||||
|
: name: Illusion of Time
|
||||||
|
: title: Illusion of Time
|
||||||
|
|
||||||
|
cartridge sha256:c341668847170d36fa5cfb720568b0b1ecbb24fc426a821f665f1d3853a46a6d
|
||||||
|
:board region=pal
|
||||||
|
: rom name=program.rom size=0x300000
|
||||||
|
: map address=00-3f,80-bf:8000-ffff
|
||||||
|
: map address=40-7d,c0-ff:0000-ffff
|
||||||
|
: ram name=save.ram size=0x2000
|
||||||
|
: map address=20-3f,a0-bf:6000-7fff mask=0xe000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNSP-AEOD-NOE
|
||||||
|
: board: SHVC-1J3M-20
|
||||||
|
: revision: 1.0
|
||||||
|
: name: Secret of Evermore
|
||||||
|
: title: Secret of Evermore
|
||||||
|
|
||||||
|
cartridge sha256:1444ab11f96f7750db992e9a4160532b27abede8a7054128c09f448300c91ebf
|
||||||
|
:board region=pal
|
||||||
|
: rom name=program.rom size=0x200000
|
||||||
|
: map address=00-3f,80-bf:8000-ffff
|
||||||
|
: map address=40-7d,c0-ff:0000-ffff
|
||||||
|
: ram name=save.ram size=0x2000
|
||||||
|
: map address=20-3f,a0-bf:6000-7fff mask=0xe000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNSP-K2-NOE
|
||||||
|
: board: SHVC-1J3M-11
|
||||||
|
: revision: 1.0
|
||||||
|
: name: Secret of Mana
|
||||||
|
: title: Secret of Mana
|
||||||
|
|
||||||
|
cartridge sha256:613cd1a31eaded18648168bd7453a57830ca9a6f3c10de5154625436fbd49556
|
||||||
|
:board region=pal
|
||||||
|
: rom name=program.rom size=0x400000
|
||||||
|
: map address=00-3f,80-bf:8000-ffff
|
||||||
|
: map address=40-7d,c0-ff:0000-ffff
|
||||||
|
: ram name=save.ram size=0x2000
|
||||||
|
: map address=20-3f,a0-bf:6000-7fff mask=0xe000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNSP-AQTD-NOE
|
||||||
|
: board: SHVC-1J3M-20
|
||||||
|
: revision: 1.1
|
||||||
|
: name: Terranigma
|
||||||
|
: title: Terranigma
|
||||||
|
|
||||||
|
cartridge sha256:f1a75578e6711716340bb26ea93bf05d5762bc7da21dbc19576fc65de1e885b5
|
||||||
|
:board region=pal
|
||||||
|
: rom name=program.rom size=0x100000
|
||||||
|
: map address=00-7d,80-ff:8000-ffff mask=0x8000
|
||||||
|
: map address=40-7d,c0-ff:0000-7fff mask=0x8000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNSP-AT-UKV
|
||||||
|
: board: SHVC-1A0N-20
|
||||||
|
: revision: 1.0
|
||||||
|
: name: Super Aleste
|
||||||
|
: title: Super Aleste
|
||||||
|
|
||||||
cartridge sha256:2ffe8828480f943056fb1ab5c3c84d48a0bf8cbe3ed7c9960b349b59adb07f3b
|
cartridge sha256:2ffe8828480f943056fb1ab5c3c84d48a0bf8cbe3ed7c9960b349b59adb07f3b
|
||||||
:board region=ntsc
|
:board region=ntsc
|
||||||
: rom name=program.rom size=0x200000
|
: rom name=program.rom size=0x200000
|
||||||
|
@ -543,8 +616,8 @@ cartridge sha256:865919b25a9d241c907bcda18b380e3c704f33f4997ad44559046f0f08c4968
|
||||||
: serial: SNS-8L-USA
|
: serial: SNS-8L-USA
|
||||||
: board: SHVC-YA0N-01
|
: board: SHVC-YA0N-01
|
||||||
: revision: 1.0
|
: revision: 1.0
|
||||||
: name: Barbie Super Star
|
: name: Barbie Super Model
|
||||||
: title: Barbie Super Star
|
: title: Barbie Super Model
|
||||||
|
|
||||||
cartridge sha256:fe1ad128313b2b9a47f89cf0d95d4c0cc2cb35a817ac5d915ee6c4d98d47d675
|
cartridge sha256:fe1ad128313b2b9a47f89cf0d95d4c0cc2cb35a817ac5d915ee6c4d98d47d675
|
||||||
:board region=ntsc
|
:board region=ntsc
|
||||||
|
@ -1759,6 +1832,19 @@ cartridge sha256:e6efb6361af04963f22c772f879a466543f56b3b6a084204fef2dcb4659d82d
|
||||||
: name: David Crane's Amazing Tennis
|
: name: David Crane's Amazing Tennis
|
||||||
: title: David Crane's Amazing Tennis
|
: title: David Crane's Amazing Tennis
|
||||||
|
|
||||||
|
cartridge sha256:300c1937e4b68108302e9b0f49974d1ec6b6c45dd8da69dddc19443f9562ecf4
|
||||||
|
:board region=ntsc
|
||||||
|
: rom name=program.rom size=0x200000
|
||||||
|
: map address=00-7d,80-ff:8000-ffff mask=0x8000
|
||||||
|
: map address=40-7d,c0-ff:0000-7fff mask=0x8000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNS-9D-USA
|
||||||
|
: board: SHVC-1A0N-20
|
||||||
|
: revision: 1.0
|
||||||
|
: name: Death and Return of Superman, The
|
||||||
|
: title: The Death and Return of Superman
|
||||||
|
|
||||||
cartridge sha256:752d24fab240f4dd1dfbfea5ec83438998316806ad44488bf8c84430ca5a2cd0
|
cartridge sha256:752d24fab240f4dd1dfbfea5ec83438998316806ad44488bf8c84430ca5a2cd0
|
||||||
:board region=ntsc
|
:board region=ntsc
|
||||||
: rom name=program.rom size=0x200000
|
: rom name=program.rom size=0x200000
|
||||||
|
@ -1886,6 +1972,21 @@ cartridge sha256:628147468c3539283197f58f03b94df49758a332831857481ea9cc31645f052
|
||||||
: name: Donkey Kong Country
|
: name: Donkey Kong Country
|
||||||
: title: Donkey Kong Country
|
: title: Donkey Kong Country
|
||||||
|
|
||||||
|
cartridge sha256:07ff03fa8c8e31d2f8277ef2a9785022edebf7f79b694c66a00c66d8e563bce5
|
||||||
|
:board region=ntsc
|
||||||
|
: rom name=program.rom size=0x400000
|
||||||
|
: map address=00-3f,80-bf:8000-ffff
|
||||||
|
: map address=40-7d,c0-ff:0000-ffff
|
||||||
|
: ram name=save.ram size=0x800
|
||||||
|
: map address=20-3f,a0-bf:6000-7fff mask=0xe000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNS-8E-USA
|
||||||
|
: board: SHVC-1J1M-20
|
||||||
|
: revision: 1.0
|
||||||
|
: name: Donkey Kong Country - Competition Cartridge
|
||||||
|
: title: Donkey Kong Country: Competition Cartridge
|
||||||
|
|
||||||
cartridge sha256:35421a9af9dd011b40b91f792192af9f99c93201d8d394026bdfb42cbf2d8633
|
cartridge sha256:35421a9af9dd011b40b91f792192af9f99c93201d8d394026bdfb42cbf2d8633
|
||||||
:board region=ntsc
|
:board region=ntsc
|
||||||
: rom name=program.rom size=0x400000
|
: rom name=program.rom size=0x400000
|
||||||
|
@ -5287,6 +5388,21 @@ cartridge sha256:2a2dc2ef84efd9a773d1e8231b7e3e57f0de7e4528968670963f2f1f358eef3
|
||||||
: name: NHL '97
|
: name: NHL '97
|
||||||
: title: NHL '97
|
: title: NHL '97
|
||||||
|
|
||||||
|
cartridge sha256:0d933149242a2a5278b9ada9294481db5b30aaa134c660951dc340bf8ab441e8
|
||||||
|
:board region=ntsc
|
||||||
|
: rom name=program.rom size=0x180000
|
||||||
|
: map address=00-3f,80-bf:8000-ffff
|
||||||
|
: map address=40-7d,c0-ff:0000-ffff
|
||||||
|
: ram name=save.ram size=0x2000
|
||||||
|
: map address=20-3f,a0-bf:6000-7fff mask=0xe000
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNS-AH7E-USA
|
||||||
|
: board: SHVC-1J3M-20
|
||||||
|
: revision: 1.1
|
||||||
|
: name: NHL '97
|
||||||
|
: title: NHL '97
|
||||||
|
|
||||||
cartridge sha256:8113c2cedafc8fd5a56c8638ae340fb275f263ff5c5e18d04dc6c3ebc5cfffee
|
cartridge sha256:8113c2cedafc8fd5a56c8638ae340fb275f263ff5c5e18d04dc6c3ebc5cfffee
|
||||||
:board region=ntsc
|
:board region=ntsc
|
||||||
: rom name=program.rom size=0x200000
|
: rom name=program.rom size=0x200000
|
||||||
|
@ -7269,6 +7385,22 @@ cartridge sha256:3857b5294ea8f7468849437bb2d8271564e8a0ff30774622e9c872bcbd53a84
|
||||||
: name: Star Fox
|
: name: Star Fox
|
||||||
: title: Star Fox
|
: title: Star Fox
|
||||||
|
|
||||||
|
cartridge sha256:2c0bac12a7866fad1cb306da768a201c12f2520332df1ef51cba1576db21ff06
|
||||||
|
:board region=ntsc
|
||||||
|
: superfx
|
||||||
|
: map address=00-3f,80-bf:3000-34ff
|
||||||
|
: rom name=program.rom size=0x100000
|
||||||
|
: map address=00-1f,80-9f:8000-ffff mask=0x8000
|
||||||
|
: ram name=save.ram size=0x8000
|
||||||
|
: map address=60-7d,e0-ff:0000-ffff
|
||||||
|
:
|
||||||
|
:information
|
||||||
|
: serial: SNS-FU-USA
|
||||||
|
: board: SHVC-1C0N
|
||||||
|
: revision: 1.0
|
||||||
|
: name: Star Fox - Super Weekend
|
||||||
|
: title: Star Fox: Super Weekend
|
||||||
|
|
||||||
cartridge sha256:3a16ad45ae3d89b13c9e53e21c2a4c725ff7cec7fbe7896d538d163f92cb4aac
|
cartridge sha256:3a16ad45ae3d89b13c9e53e21c2a4c725ff7cec7fbe7896d538d163f92cb4aac
|
||||||
:board region=ntsc
|
:board region=ntsc
|
||||||
: rom name=program.rom size=0x100000
|
: rom name=program.rom size=0x100000
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <nall/function.hpp>
|
||||||
|
#include <nall/string.hpp>
|
||||||
|
|
||||||
|
namespace nall { namespace chrono { namespace {
|
||||||
|
|
||||||
|
//passage of time functions (from unknown epoch)
|
||||||
|
|
||||||
|
auto nanosecond() -> uint64_t {
|
||||||
|
timespec tv;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &tv);
|
||||||
|
return tv.tv_sec * 1'000'000'000 + tv.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto microsecond() -> uint64_t { return nanosecond() / 1'000; }
|
||||||
|
auto millisecond() -> uint64_t { return nanosecond() / 1'000'000; }
|
||||||
|
auto second() -> uint64_t { return nanosecond() / 1'000'000'000; }
|
||||||
|
|
||||||
|
auto benchmark(const function<void ()>& f, uint64_t times = 1) -> void {
|
||||||
|
auto start = nanosecond();
|
||||||
|
while(times--) f();
|
||||||
|
auto end = nanosecond();
|
||||||
|
print("[chrono::benchmark] ", (double)(end - start) / 1'000'000'000.0, "s\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
//exact date/time functions (from system epoch)
|
||||||
|
|
||||||
|
struct timeinfo {
|
||||||
|
timeinfo(
|
||||||
|
uint year = 0, uint month = 0, uint day = 0,
|
||||||
|
uint hour = 0, uint minute = 0, uint second = 0, uint weekday = 0
|
||||||
|
) : year(year), month(month), day(day),
|
||||||
|
hour(hour), minute(minute), second(second), weekday(weekday) {
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const { return month; }
|
||||||
|
|
||||||
|
uint year; //...
|
||||||
|
uint month; //1 - 12
|
||||||
|
uint day; //1 - 31
|
||||||
|
uint hour; //0 - 23
|
||||||
|
uint minute; //0 - 59
|
||||||
|
uint second; //0 - 60
|
||||||
|
uint weekday; //0 - 6
|
||||||
|
};
|
||||||
|
|
||||||
|
auto timestamp() -> uint64_t {
|
||||||
|
return ::time(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace utc {
|
||||||
|
auto timeinfo(uint64_t time = 0) -> chrono::timeinfo {
|
||||||
|
auto stamp = time ? (time_t)time : (time_t)timestamp();
|
||||||
|
auto info = gmtime(&stamp);
|
||||||
|
return {
|
||||||
|
(uint)info->tm_year + 1900,
|
||||||
|
(uint)info->tm_mon + 1,
|
||||||
|
(uint)info->tm_mday,
|
||||||
|
(uint)info->tm_hour,
|
||||||
|
(uint)info->tm_min,
|
||||||
|
(uint)info->tm_sec,
|
||||||
|
(uint)info->tm_wday
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto year(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).year, 4, '0'); }
|
||||||
|
auto month(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).month, 2, '0'); }
|
||||||
|
auto day(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).day, 2, '0'); }
|
||||||
|
auto hour(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).hour, 2, '0'); }
|
||||||
|
auto minute(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).minute, 2, '0'); }
|
||||||
|
auto second(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).second, 2, '0'); }
|
||||||
|
|
||||||
|
auto date(uint64_t timestamp = 0) -> string {
|
||||||
|
auto t = timeinfo(timestamp);
|
||||||
|
return {pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0')};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto time(uint64_t timestamp = 0) -> string {
|
||||||
|
auto t = timeinfo(timestamp);
|
||||||
|
return {pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0')};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto datetime(uint64_t timestamp = 0) -> string {
|
||||||
|
auto t = timeinfo(timestamp);
|
||||||
|
return {
|
||||||
|
pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0'), " ",
|
||||||
|
pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace local {
|
||||||
|
auto timeinfo(uint64_t time = 0) -> chrono::timeinfo {
|
||||||
|
auto stamp = time ? (time_t)time : (time_t)timestamp();
|
||||||
|
auto info = localtime(&stamp);
|
||||||
|
return {
|
||||||
|
(uint)info->tm_year + 1900,
|
||||||
|
(uint)info->tm_mon + 1,
|
||||||
|
(uint)info->tm_mday,
|
||||||
|
(uint)info->tm_hour,
|
||||||
|
(uint)info->tm_min,
|
||||||
|
(uint)info->tm_sec,
|
||||||
|
(uint)info->tm_wday
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto year(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).year, 4, '0'); }
|
||||||
|
auto month(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).month, 2, '0'); }
|
||||||
|
auto day(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).day, 2, '0'); }
|
||||||
|
auto hour(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).hour, 2, '0'); }
|
||||||
|
auto minute(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).minute, 2, '0'); }
|
||||||
|
auto second(uint64_t timestamp = 0) -> string { return pad(timeinfo(timestamp).second, 2, '0'); }
|
||||||
|
|
||||||
|
auto date(uint64_t timestamp = 0) -> string {
|
||||||
|
auto t = timeinfo(timestamp);
|
||||||
|
return {pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0')};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto time(uint64_t timestamp = 0) -> string {
|
||||||
|
auto t = timeinfo(timestamp);
|
||||||
|
return {pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0')};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto datetime(uint64_t timestamp = 0) -> string {
|
||||||
|
auto t = timeinfo(timestamp);
|
||||||
|
return {
|
||||||
|
pad(t.year, 4, '0'), "-", pad(t.month, 2, '0'), "-", pad(t.day, 2, '0'), " ",
|
||||||
|
pad(t.hour, 2, '0'), ":", pad(t.minute, 2, '0'), ":", pad(t.second, 2, '0')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}}}
|
110
nall/config.hpp
110
nall/config.hpp
|
@ -1,110 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <nall/platform.hpp>
|
|
||||||
#include <nall/file.hpp>
|
|
||||||
#include <nall/string.hpp>
|
|
||||||
|
|
||||||
namespace nall {
|
|
||||||
namespace Configuration {
|
|
||||||
|
|
||||||
struct Node {
|
|
||||||
string name;
|
|
||||||
string desc;
|
|
||||||
enum class Type : uint { Null, Boolean, Integer, Natural, Double, String } type = Type::Null;
|
|
||||||
void* data = nullptr;
|
|
||||||
vector<Node> children;
|
|
||||||
|
|
||||||
explicit operator bool() const { return data; }
|
|
||||||
|
|
||||||
auto get() const -> string {
|
|
||||||
switch(type) {
|
|
||||||
case Type::Boolean: return {*(bool*)data};
|
|
||||||
case Type::Integer: return {*(int*)data};
|
|
||||||
case Type::Natural: return {*(uint*)data};
|
|
||||||
case Type::Double: return {*(double*)data};
|
|
||||||
case Type::String: return {*(string*)data};
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto set(const string& value) -> void {
|
|
||||||
switch(type) {
|
|
||||||
case Type::Boolean: *(bool*)data = (value != "false"); break;
|
|
||||||
case Type::Integer: *(int*)data = toInteger(value); break;
|
|
||||||
case Type::Natural: *(uint*)data = toNatural(value); break;
|
|
||||||
case Type::Double: *(double*)data = toReal(value); break;
|
|
||||||
case Type::String: *(string*)data = value; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto assign() { type = Type::Null; data = nullptr; }
|
|
||||||
auto assign(bool& bind) { type = Type::Boolean; data = (void*)&bind; }
|
|
||||||
auto assign(int& bind) { type = Type::Integer; data = (void*)&bind; }
|
|
||||||
auto assign(uint& bind) { type = Type::Natural; data = (void*)&bind; }
|
|
||||||
auto assign(double& bind) { type = Type::Double; data = (void*)&bind; }
|
|
||||||
auto assign(string& bind) { type = Type::String; data = (void*)&bind; }
|
|
||||||
auto assign(const Node& node) { operator=(node); }
|
|
||||||
|
|
||||||
template<typename T> auto append(T& data, const string& name, const string& desc = "") -> void {
|
|
||||||
Node node;
|
|
||||||
node.assign(data);
|
|
||||||
node.name = name;
|
|
||||||
node.desc = desc;
|
|
||||||
children.append(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto find(const string& path) -> maybe<Node&> {
|
|
||||||
auto p = path.split("/");
|
|
||||||
auto name = p.takeLeft();
|
|
||||||
for(auto& child : children) {
|
|
||||||
if(child.name == name) {
|
|
||||||
if(p.size() == 0) return child;
|
|
||||||
return child.find(p.merge("/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto load(Markup::Node path) -> void {
|
|
||||||
for(auto& child : children) {
|
|
||||||
if(auto leaf = path[child.name]) {
|
|
||||||
if(child) child.set(leaf.text());
|
|
||||||
child.load(leaf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto save(file& fp, uint depth = 0) -> void {
|
|
||||||
for(auto& child : children) {
|
|
||||||
if(child.desc) {
|
|
||||||
for(auto n : range(depth)) fp.print(" ");
|
|
||||||
fp.print("//", child.desc, "\n");
|
|
||||||
}
|
|
||||||
for(auto n : range(depth)) fp.print(" ");
|
|
||||||
fp.print(child.name);
|
|
||||||
if(child) fp.print(": ", child.get());
|
|
||||||
fp.print("\n");
|
|
||||||
child.save(fp, depth + 1);
|
|
||||||
if(depth == 0) fp.print("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Document : Node {
|
|
||||||
auto load(const string& filename) -> bool {
|
|
||||||
if(!file::exists(filename)) return false;
|
|
||||||
auto document = BML::unserialize(string::read(filename));
|
|
||||||
Node::load(document);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto save(const string& filename) -> bool {
|
|
||||||
file fp(filename, file::mode::write);
|
|
||||||
if(!fp.open()) return false;
|
|
||||||
Node::save(fp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -230,7 +230,7 @@ auto Response::setData(const vector<uint8_t>& value) -> type& {
|
||||||
|
|
||||||
auto Response::setFile(const string& value) -> type& {
|
auto Response::setFile(const string& value) -> type& {
|
||||||
_file = value;
|
_file = value;
|
||||||
string eTag = {"\"", string::datetime(file::timestamp(value, file::time::modify)), "\""};
|
string eTag = {"\"", chrono::utc::datetime(file::timestamp(value, file::time::modify)), "\""};
|
||||||
header.assign("Content-Length", file::size(value));
|
header.assign("Content-Length", file::size(value));
|
||||||
header.assign("Cache-Control", "public");
|
header.assign("Cache-Control", "public");
|
||||||
header.assign("ETag", eTag);
|
header.assign("ETag", eTag);
|
||||||
|
|
|
@ -45,7 +45,7 @@ struct inode {
|
||||||
return data.st_mode;
|
return data.st_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto timestamp(const string& name, time mode = time::modify) -> time_t {
|
static auto timestamp(const string& name, time mode = time::modify) -> uint64_t {
|
||||||
struct stat data = {0};
|
struct stat data = {0};
|
||||||
stat(name, &data);
|
stat(name, &data);
|
||||||
switch(mode) { default:
|
switch(mode) { default:
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
#include <nall/bit.hpp>
|
#include <nall/bit.hpp>
|
||||||
#include <nall/bit-field.hpp>
|
#include <nall/bit-field.hpp>
|
||||||
#include <nall/bit-vector.hpp>
|
#include <nall/bit-vector.hpp>
|
||||||
#include <nall/config.hpp>
|
#include <nall/chrono.hpp>
|
||||||
#include <nall/directory.hpp>
|
#include <nall/directory.hpp>
|
||||||
#include <nall/dl.hpp>
|
#include <nall/dl.hpp>
|
||||||
#include <nall/endian.hpp>
|
#include <nall/endian.hpp>
|
||||||
|
|
|
@ -62,7 +62,7 @@ template<typename T> struct stringify;
|
||||||
//format.hpp
|
//format.hpp
|
||||||
template<typename... P> inline auto print(P&&...) -> void;
|
template<typename... P> inline auto print(P&&...) -> void;
|
||||||
template<typename... P> inline auto print(FILE*, P&&...) -> void;
|
template<typename... P> inline auto print(FILE*, P&&...) -> void;
|
||||||
template<typename T> inline auto numeral(T value, long precision = 0, char padchar = '0') -> string;
|
template<typename T> inline auto pad(const T& value, long precision = 0, char padchar = ' ') -> string;
|
||||||
inline auto hex(uintmax_t value, long precision = 0, char padchar = '0') -> string;
|
inline auto hex(uintmax_t value, long precision = 0, char padchar = '0') -> string;
|
||||||
inline auto octal(uintmax_t value, long precision = 0, char padchar = '0') -> string;
|
inline auto octal(uintmax_t value, long precision = 0, char padchar = '0') -> string;
|
||||||
inline auto binary(uintmax_t value, long precision = 0, char padchar = '0') -> string;
|
inline auto binary(uintmax_t value, long precision = 0, char padchar = '0') -> string;
|
||||||
|
@ -183,11 +183,6 @@ public:
|
||||||
template<typename T> inline auto _append(const stringify<T>&) -> string&;
|
template<typename T> inline auto _append(const stringify<T>&) -> string&;
|
||||||
inline auto length() const -> uint;
|
inline auto length() const -> uint;
|
||||||
|
|
||||||
//datetime.hpp
|
|
||||||
inline static auto date(time_t = 0) -> string;
|
|
||||||
inline static auto time(time_t = 0) -> string;
|
|
||||||
inline static auto datetime(time_t = 0) -> string;
|
|
||||||
|
|
||||||
//find.hpp
|
//find.hpp
|
||||||
template<bool, bool> inline auto _find(int, string_view) const -> maybe<uint>;
|
template<bool, bool> inline auto _find(int, string_view) const -> maybe<uint>;
|
||||||
|
|
||||||
|
@ -316,7 +311,6 @@ struct string_format : vector<string> {
|
||||||
#include <nall/string/compare.hpp>
|
#include <nall/string/compare.hpp>
|
||||||
#include <nall/string/convert.hpp>
|
#include <nall/string/convert.hpp>
|
||||||
#include <nall/string/core.hpp>
|
#include <nall/string/core.hpp>
|
||||||
#include <nall/string/datetime.hpp>
|
|
||||||
#include <nall/string/find.hpp>
|
#include <nall/string/find.hpp>
|
||||||
#include <nall/string/format.hpp>
|
#include <nall/string/format.hpp>
|
||||||
#include <nall/string/list.hpp>
|
#include <nall/string/list.hpp>
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace nall {
|
|
||||||
|
|
||||||
auto string::date(time_t timestamp) -> string {
|
|
||||||
if(timestamp == 0) timestamp = ::time(nullptr);
|
|
||||||
tm* info = localtime(×tamp);
|
|
||||||
return {
|
|
||||||
numeral(1900 + info->tm_year, 4L), "-",
|
|
||||||
numeral(1 + info->tm_mon, 2L), "-",
|
|
||||||
numeral(info->tm_mday, 2L)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto string::time(time_t timestamp) -> string {
|
|
||||||
if(timestamp == 0) timestamp = ::time(nullptr);
|
|
||||||
tm* info = localtime(×tamp);
|
|
||||||
return {
|
|
||||||
numeral(info->tm_hour, 2L), ":",
|
|
||||||
numeral(info->tm_min, 2L), ":",
|
|
||||||
numeral(info->tm_sec, 2L)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto string::datetime(time_t timestamp) -> string {
|
|
||||||
if(timestamp == 0) timestamp = ::time(nullptr);
|
|
||||||
return {string::date(timestamp), " ", string::time(timestamp)};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -78,44 +78,7 @@ template<typename... P> auto print(FILE* fp, P&&... p) -> void {
|
||||||
fwrite(s.data(), 1, s.size(), fp);
|
fwrite(s.data(), 1, s.size(), fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
template<typename T> auto pad(const T& value, long precision, char padchar) -> string {
|
||||||
auto integer(intmax_t value, long precision, char padchar) -> string {
|
|
||||||
string buffer;
|
|
||||||
buffer.resize(1 + sizeof(intmax_t) * 3);
|
|
||||||
char* p = buffer.get();
|
|
||||||
|
|
||||||
bool negative = value < 0;
|
|
||||||
if(negative) value = -value; //make positive
|
|
||||||
uint size = 0;
|
|
||||||
do {
|
|
||||||
p[size++] = '0' + (value % 10);
|
|
||||||
value /= 10;
|
|
||||||
} while(value);
|
|
||||||
if(negative) p[size++] = '-';
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reverse();
|
|
||||||
if(precision) buffer.size(precision, padchar);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto natural(uintmax_t value, long precision, char padchar) -> string {
|
|
||||||
string buffer;
|
|
||||||
buffer.resize(sizeof(uintmax_t) * 3);
|
|
||||||
char* p = buffer.get();
|
|
||||||
|
|
||||||
uint size = 0;
|
|
||||||
do {
|
|
||||||
p[size++] = '0' + (value % 10);
|
|
||||||
value /= 10;
|
|
||||||
} while(value);
|
|
||||||
buffer.resize(size);
|
|
||||||
buffer.reverse();
|
|
||||||
if(precision) buffer.size(precision, padchar);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
template<typename T> auto numeral(T value, long precision, char padchar) -> string {
|
|
||||||
string buffer{value};
|
string buffer{value};
|
||||||
if(precision) buffer.size(precision, padchar);
|
if(precision) buffer.size(precision, padchar);
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|
Loading…
Reference in New Issue