1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
|
#include "backgrounds.hh"
#include "chrono.hh"
#include "config.hh"
#include "controllers.hh"
#include "database.hh"
#include "engine.hh"
#include "fs.hh"
#include "glutil.hh"
#include "i18n.hh"
#include "log.hh"
#include "platform.hh"
#include "profiler.hh"
#include "screen.hh"
#include "songs.hh"
#include "video_driver.hh"
#include "webcam.hh"
#include "webserver.hh"
// Screens
#include "screen_intro.hh"
#include "screen_songs.hh"
#include "screen_sing.hh"
#include "screen_practice.hh"
#include "screen_audiodevices.hh"
#include "screen_paths.hh"
#include "screen_players.hh"
#include "screen_playlist.hh"
#include <boost/program_options.hpp>
#include <cstdlib>
#include <csignal>
#include <string>
#include <thread>
#include <vector>
// Disable main level exception handling for debug builds (because gdb cannot properly catch throwing otherwise)
#ifdef NDEBUG
#define RUNTIME_ERROR std::runtime_error
#define EXCEPTION std::exception
#else
namespace { struct Nothing { char const* what() const { return nullptr; } }; }
#define RUNTIME_ERROR Nothing
#define EXCEPTION Nothing
#endif
std::atomic<bool> g_quit{ false };
bool g_take_screenshot = false;
// Signal handling for Ctrl-C
static void signalSetup();
extern "C" void quit(int) {
using namespace std; // Apparently some implementations put quick_exit in std:: and others in ::
if (g_quit) abort(); // Instant exit if Ctrl+C is pressed again
g_quit = true;
signalSetup();
}
static void signalSetup() {
std::signal(SIGINT, quit);
std::signal(SIGTERM, quit);
}
/// can be thrown as an exception to quit the game
struct QuitNow {};
static void checkEvents(Game& gm, Time eventTime) {
if (g_quit) {
std::cerr << "Terminating, please wait... (or kill the process)" << std::endl;
throw QuitNow();
}
Window& window = gm.window();
SDL_Event event;
while (SDL_PollEvent(&event) == 1) {
// Let the navigation system grab any and all SDL events
gm.controllers.pushEvent(event, eventTime);
auto type = event.type;
if (type == SDL_WINDOWEVENT) window.event(event.window.event);
if (type == SDL_QUIT) gm.finished();
if (type == SDL_KEYDOWN) {
auto key = event.key.keysym.scancode;
auto mod = event.key.keysym.mod;
bool altEnter = (key == SDL_SCANCODE_RETURN || key == SDL_SCANCODE_KP_ENTER) && mod & KMOD_ALT; // Alt+Enter
bool modF = key == SDL_SCANCODE_F && mod & KMOD_CTRL && mod & KMOD_GUI; // MacOS Ctrl+Cmd+F
if (altEnter || modF || key == SDL_SCANCODE_F11) {
config["graphic/fullscreen"].b() = !config["graphic/fullscreen"].b();
continue; // Already handled here...
}
if (key == SDL_SCANCODE_PRINTSCREEN || (key == SDL_SCANCODE_F12 && (mod & Platform::shortcutModifier()))) {
g_take_screenshot = true;
continue; // Already handled here...
}
if (key == SDL_SCANCODE_F4 && mod & KMOD_ALT) {
gm.finished();
continue; // Already handled here...
}
}
// Screens always receive SDL events that were not already handled here
gm.getCurrentScreen()->manageEvent(event);
}
for (input::NavEvent event; gm.controllers.getNav(event); ) {
input::NavButton nav = event.button;
// Volume control
if (nav == input::NAV_VOLUME_UP || nav == input::NAV_VOLUME_DOWN) {
std::string curS = gm.getCurrentScreen()->getName();
// Pick proper setting
std::string which_vol = (curS == "Sing" || curS == "Practice")
? "audio/music_volume" : "audio/preview_volume";
// Adjust value
if (nav == input::NAV_VOLUME_UP) ++config[which_vol]; else --config[which_vol];
// Show message
gm.flashMessage(config[which_vol].getShortDesc() + ": " + config[which_vol].getValue());
continue; // Already handled here...
}
// If a dialog is open, any nav event will close it
if (gm.isDialogOpen()) { gm.closeDialog(); }
// Let the current screen handle other events
gm.getCurrentScreen()->manageEvent(event);
}
// Need to toggle full screen mode or adjust resolution?
window.resize();
}
void mainLoop(std::string const& songlist) {
Platform platform;
std::clog << "core/notice: Starting the audio subsystem (errors printed on console may be ignored)." << std::endl;
Audio audio;
std::clog << "core/info: Loading assets." << std::endl;
TranslationEngine localization(PACKAGE);
Window window;
TextureLoader m_loader;
Backgrounds backgrounds;
Database database(getConfigDir() / "database.xml");
Songs songs(database, songlist);
loadFonts();
Game gm(window, audio);
WebServer server(songs);
try {
// Load audio samples
gm.loading(_("Loading audio samples..."), 0.5);
audio.loadSample("drum bass", findFile("sounds/drum_bass.ogg"));
audio.loadSample("drum snare", findFile("sounds/drum_snare.ogg"));
audio.loadSample("drum hi-hat", findFile("sounds/drum_hi-hat.ogg"));
audio.loadSample("drum tom1", findFile("sounds/drum_tom1.ogg"));
audio.loadSample("drum cymbal", findFile("sounds/drum_cymbal.ogg"));
//audio.loadSample("drum tom2", findFile("sounds/drum_tom2.ogg"));
audio.loadSample("guitar fail1", findFile("sounds/guitar_fail1.ogg"));
audio.loadSample("guitar fail2", findFile("sounds/guitar_fail2.ogg"));
audio.loadSample("guitar fail3", findFile("sounds/guitar_fail3.ogg"));
audio.loadSample("guitar fail4", findFile("sounds/guitar_fail4.ogg"));
audio.loadSample("guitar fail5", findFile("sounds/guitar_fail5.ogg"));
audio.loadSample("guitar fail6", findFile("sounds/guitar_fail6.ogg"));
audio.loadSample("notice.ogg",findFile("notice.ogg"));
// Load screens
gm.loading(_("Creating screens..."), 0.7);
gm.addScreen(std::make_unique<ScreenIntro>("Intro", audio));
gm.addScreen(std::make_unique<ScreenSongs>("Songs", audio, songs, database));
gm.addScreen(std::make_unique<ScreenSing>("Sing", audio, database, backgrounds));
gm.addScreen(std::make_unique<ScreenPractice>("Practice", audio));
gm.addScreen(std::make_unique<ScreenAudioDevices>("AudioDevices", audio));
gm.addScreen(std::make_unique<ScreenPaths>("Paths", audio, songs));
gm.addScreen(std::make_unique<ScreenPlayers>("Players", audio, database));
gm.addScreen(std::make_unique<ScreenPlaylist>("Playlist", audio, songs, backgrounds));
gm.activateScreen("Intro");
gm.loading(_("Entering main menu"), 0.8);
gm.updateScreen(); // exit/enter, any exception is fatal error
gm.loading(_("Loading complete"), 1.0);
// Main loop
auto time = Clock::now();
unsigned frames = 0;
std::clog << "core/info: Assets loaded, entering main loop." << std::endl;
while (!gm.isFinished()) {
Profiler prof("mainloop");
bool benchmarking = config["graphic/fps"].b();
if (songs.doneLoading == true && songs.displayedAlert == false) {
gm.dialog(_("Done Loading!\n Loaded ") + std::to_string(songs.loadedSongs()) + " Songs.");
songs.displayedAlert = true;
}
if( g_take_screenshot ) {
try {
window.screenshot();
gm.flashMessage(_("Screenshot taken!"));
} catch (EXCEPTION& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
gm.flashMessage(_("Screenshot failed!"));
}
g_take_screenshot = false;
}
gm.updateScreen(); // exit/enter, any exception is fatal error
if (benchmarking) prof("misc");
try {
window.blank();
// Draw
window.render([&gm]{ gm.drawScreen(); });
if (benchmarking) { glFinish(); prof("draw"); }
// Display (and wait until next frame)
window.swap();
if (benchmarking) { glFinish(); prof("swap"); }
updateTextures();
gm.prepareScreen();
if (benchmarking) { glFinish(); prof("textures"); }
if (benchmarking) {
++frames;
if (Clock::now() - time > 1s) {
std::ostringstream oss;
oss << frames << " FPS";
gm.flashMessage(oss.str());
time += 1s;
frames = 0;
}
} else {
std::this_thread::sleep_until(time + 10ms); // Max 100 FPS
time = Clock::now();
frames = 0;
}
if (benchmarking) prof("fpsctrl");
// Process events for the next frame
auto eventTime = Clock::now();
gm.controllers.process(eventTime);
checkEvents(gm, eventTime);
if (benchmarking) prof("events");
} catch (RUNTIME_ERROR& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
gm.flashMessage(std::string("ERROR: ") + e.what());
}
}
writeConfig();
} catch (EXCEPTION& e) {
std::clog << "core/error: Exiting due to fatal error: " << e.what() << std::endl;
gm.fatalError(e.what()); // Notify the user
throw;
} catch (QuitNow&) {
std::cerr << "Terminated." << std::endl;
}
}
/// Simple test utility to make mapping of joystick buttons/axes easier
void jstestLoop() {
try {
config["graphic/fullscreen"].b() = false;
config["graphic/window_width"].i() = 640;
config["graphic/window_height"].i() = 360;
Window window;
// Main loop
int oldjoy = -1, oldaxis = -1, oldvalue = -1;
while (true) {
SDL_Event e;
while(SDL_PollEvent(&e) == 1) {
if (e.type == SDL_QUIT || (e.type == SDL_KEYDOWN && e.key.keysym.scancode == SDL_SCANCODE_ESCAPE)) {
return;
} else if (e.type == SDL_KEYDOWN) {
std::cout << "Keyboard key: " << int(e.key.keysym.scancode) << ", mod: " << int(e.key.keysym.mod) << std::endl;
} else if (e.type == SDL_JOYBUTTONDOWN) {
std::cout << "JoyID: " << int(e.jbutton.which) << ", button: " << int(e.jbutton.button) << ", state: " << int(e.jbutton.state) << std::endl;
} else if (e.type == SDL_JOYAXISMOTION) {
if ((oldjoy != int(e.jaxis.which)) || (oldaxis != int(e.jaxis.axis)) || (oldvalue != int(e.jaxis.value))) {
std::cout << "JoyID: " << int(e.jaxis.which) << ", axis: " << int(e.jaxis.axis) << ", value: " << int(e.jaxis.value) << std::endl;
oldjoy = int(e.jaxis.which);
oldaxis = int(e.jaxis.axis);
oldvalue = int(e.jaxis.value);
}
} else if (e.type == SDL_JOYHATMOTION) {
std::cout << "JoyID: " << int(e.jhat.which) << ", hat: " << int(e.jhat.hat) << ", value: " << int(e.jhat.value) << std::endl;
}
}
window.blank(); window.swap();
std::this_thread::sleep_for(10ms); // Max 100 FPS
}
} catch (EXCEPTION& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
} catch (QuitNow&) {
std::cerr << "Terminated." << std::endl;
}
return;
}
template <typename Container> void confOverride(Container const& c, std::string const& name) {
if (c.empty()) return; // Don't override if no options specified
ConfigItem::StringList& sl = config[name].sl();
sl.clear();
std::copy(c.begin(), c.end(), std::back_inserter(sl));
}
void outputOptionalFeatureStatus();
void fatalError(std::string msg, bool hasLog = false, std::string title = "FATAL ERROR") {
std::ostringstream errMsg;
errMsg << msg;
if (hasLog) {
errMsg << std::endl << "More details might be available in " << getLogFilename() << ".";
}
errMsg << std::endl << "If you think this is a bug in Performous, please report it at "
<< std::endl << " https://github.com/performous/performous/issues";
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.c_str(),
errMsg.str().c_str(), nullptr);
std::cerr << title << ": " << msg << std::endl;
if (hasLog) {
std::clog << "core/error: " << errMsg.str() << std::endl;
}
}
int main(int argc, char** argv) try {
signalSetup();
std::srand(std::time(nullptr));
// Parse commandline options
std::vector<std::string> devices;
std::vector<std::string> songdirs;
namespace po = boost::program_options;
po::options_description opt1("Generic options");
std::string songlist;
std::string loglevel;
opt1.add_options()
("help,h", "you are viewing it")
("log,l", po::value<std::string>(&loglevel), "subsystem name or minimum level to log")
("version,v", "display version number")
("songlist", po::value<std::string>(&songlist), "save a list of songs in the specified folder");
po::options_description opt2("Configuration options");
opt2.add_options()
("audio", po::value<std::vector<std::string> >(&devices)->composing(), "specify an audio device to use")
("audiohelp", "print audio related information")
("jstest", "utility to get joystick button mappings");
po::options_description opt3("Hidden options");
opt3.add_options()
("songdir", po::value<std::vector<std::string> >(&songdirs)->composing(), "");
// Process flagless options as songdirs
po::positional_options_description p;
p.add("songdir", -1);
po::options_description cmdline;
cmdline.add(opt1).add(opt2);
po::variables_map vm;
// Load the arguments
try {
po::options_description allopts(cmdline);
allopts.add(opt3);
po::store(po::command_line_parser(argc, argv).options(allopts).positional(p).run(), vm);
} catch (EXCEPTION& e) {
std::cerr << cmdline << std::endl;
std::cerr << "ERROR: " << e.what() << std::endl;
return EXIT_FAILURE;
}
po::notify(vm);
if (vm.count("version")) {
std::cout << PACKAGE " " VERSION << std::endl;
return EXIT_SUCCESS;
}
if (vm.count("help")) {
std::cout << cmdline << " any arguments without a switch are interpreted as song folders.\n" << std::endl;
return EXIT_SUCCESS;
}
Logger logger(loglevel);
try {
outputOptionalFeatureStatus();
// Read config files
readConfig();
if (vm.count("audiohelp")) {
std::clog << "core/notice: Starting audio subsystem for audiohelp (errors printed on console may be ignored)." << std::endl;
Audio audio;
// Print the devices
std::cout << portaudio::AudioBackends().dump();
// Some examples
std::cout << "Example --audio parameters" << std::endl;
std::cout << " --audio \"out=2\" # Pick first working two-channel playback device" << std::endl;
std::cout << " --audio \"dev=1 out=2\" # Pick device id 1 and assign stereo playback" << std::endl;
std::cout << " --audio 'dev=\"HDA Intel\" mics=blue,red' # HDA Intel with two mics" << std::endl;
std::cout << " --audio 'dev=pulse out=2 mics=blue' # PulseAudio with input and output" << std::endl;
return EXIT_SUCCESS;
}
// Override XML config for options that were specified from commandline or performous.conf
confOverride(songdirs, "paths/songs");
confOverride(devices, "audio/devices");
getPaths(); // Initialize paths before other threads start
if (vm.count("jstest")) { // Joystick test program
std::clog << "core/notice: Starting jstest input test utility." << std::endl;
std::cout << std::endl << "Joystick utility - Touch your joystick to see buttons here" << std::endl
<< "Hit ESC (window focused) to quit" << std::endl << std::endl;
jstestLoop();
return EXIT_SUCCESS;
}
// Run the game init and main loop
mainLoop(songlist);
return EXIT_SUCCESS; // Do not remove. SDL_Main (which this function is called on some platforms) needs return statement.
} catch (EXCEPTION& e) {
// After logging is initialized, we can also inform the user about the log file.
fatalError(e.what(), true);
return EXIT_FAILURE;
}
} catch (EXCEPTION& e) {
fatalError(e.what());
return EXIT_FAILURE;
}
void outputOptionalFeatureStatus() {
std::clog << "core/notice: " PACKAGE " " VERSION " starting..."
<< "\n Internationalization: " << (TranslationEngine::enabled() ? "Enabled" : "Disabled")
<< "\n MIDI Hardware I/O: " << (input::Hardware::midiEnabled() ? "Enabled" : "Disabled")
<< "\n Webcam support: " << (Webcam::enabled() ? "Enabled" : "Disabled")
<< std::endl;
}
|