File: main.cc

package info (click to toggle)
performous 1.1%2Bgit20181118-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 11,736 kB
  • sloc: cpp: 30,008; ansic: 2,751; sh: 801; xml: 464; python: 374; makefile: 36
file content (406 lines) | stat: -rw-r--r-- 15,266 bytes parent folder | download | duplicates (3)
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;
}