mirror of https://github.com/adamdruppe/arsd.git
moar audio code overhauling for reliability
This commit is contained in:
parent
a6af6c4bab
commit
f4c52cefa6
94
game.d
94
game.d
|
@ -99,11 +99,43 @@ SimpleWindow create2dWindow(string title, int width = 512, int height = 512) {
|
|||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
window.windowResized = (newWidth, newHeight) {
|
||||
int x, y, w, h;
|
||||
|
||||
// FIXME: this works for only square original sizes
|
||||
if(newWidth < newHeight) {
|
||||
w = newWidth;
|
||||
h = newWidth * height / width;
|
||||
x = 0;
|
||||
y = (newHeight - h) / 2;
|
||||
} else {
|
||||
w = newHeight * width / height;
|
||||
h = newHeight;
|
||||
x = (newWidth - w) / 2;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
glViewport(x, y, w, h);
|
||||
window.redrawOpenGlSceneNow();
|
||||
};
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
/// This is the base class for your game.
|
||||
class GameHelperBase {
|
||||
/++
|
||||
This is the base class for your game.
|
||||
|
||||
You should destroy this explicitly. Easiest
|
||||
way is to do this in your `main` function:
|
||||
|
||||
---
|
||||
auto game = new MyGameSubclass();
|
||||
scope(exit) .destroy(game);
|
||||
|
||||
runGame(game);
|
||||
---
|
||||
+/
|
||||
abstract class GameHelperBase {
|
||||
/// Implement this to draw.
|
||||
abstract void drawFrame();
|
||||
|
||||
|
@ -125,12 +157,10 @@ class GameHelperBase {
|
|||
bool wantAudio() { return false; }
|
||||
|
||||
/// You must override [wantAudio] and return true for this to be valid;
|
||||
AudioPcmOutThread audio;
|
||||
AudioOutputThread audio;
|
||||
|
||||
this() {
|
||||
if(wantAudio) {
|
||||
audio = new AudioPcmOutThread();
|
||||
}
|
||||
audio = AudioOutputThread(wantAudio());
|
||||
}
|
||||
|
||||
protected bool redrawForced;
|
||||
|
@ -202,6 +232,8 @@ struct VirtualController {
|
|||
Select, Start, L, R
|
||||
}
|
||||
|
||||
@nogc pure nothrow @safe:
|
||||
|
||||
/++
|
||||
History: Added April 30, 2020
|
||||
+/
|
||||
|
@ -231,23 +263,43 @@ struct VirtualController {
|
|||
}
|
||||
}
|
||||
|
||||
/// The max rates are given in executions per second
|
||||
/// Redraw will never be called unless there has been at least one update
|
||||
void runGame(T : GameHelperBase)(T game, int maxUpdateRate = 20, int maxRedrawRate = 0) {
|
||||
/++
|
||||
Deprecated, use the other overload instead.
|
||||
|
||||
History:
|
||||
Deprecated on May 9, 2020. Instead of calling
|
||||
`runGame(your_instance);` run `runGame!YourClass();`
|
||||
instead. If you needed to change something in the game
|
||||
ctor, make a default constructor in your class to do that
|
||||
instead.
|
||||
+/
|
||||
deprecated("Use runGame!YourGameType(updateRate, redrawRate); instead now.")
|
||||
void runGame()(GameHelperBase game, int maxUpdateRate = 20, int maxRedrawRate = 0) { assert(0, "this overload is deprecated, use runGame!YourClass instead"); }
|
||||
|
||||
/++
|
||||
Runs your game. It will construct the given class and destroy it at end of scope.
|
||||
Your class must have a default constructor and must implement [GameHelperBase].
|
||||
Your class should also probably be `final` for performance reasons.
|
||||
|
||||
$(TIP
|
||||
If you need to pass parameters to your game class, you can define
|
||||
it as a nested class in your `main` function and access the local
|
||||
variables that way instead of passing them explicitly through the
|
||||
constructor.
|
||||
)
|
||||
|
||||
Params:
|
||||
maxUpdateRate = The max rates are given in executions per second
|
||||
maxRedrawRate = Redraw will never be called unless there has been at least one update
|
||||
+/
|
||||
void runGame(T : GameHelperBase)(int maxUpdateRate = 20, int maxRedrawRate = 0) {
|
||||
|
||||
|
||||
auto game = new T();
|
||||
scope(exit) .destroy(game);
|
||||
|
||||
// this is a template btw because then it can statically dispatch
|
||||
// the members instead of going through the virtual interface.
|
||||
if(game.audio !is null) {
|
||||
game.audio.start();
|
||||
}
|
||||
|
||||
scope(exit)
|
||||
if(game.audio !is null) {
|
||||
import std.stdio;
|
||||
game.audio.stop();
|
||||
game.audio.join();
|
||||
game.audio = null;
|
||||
}
|
||||
|
||||
|
||||
int joystickPlayers = enableJoystickInput();
|
||||
scope(exit) closeJoysticks();
|
||||
|
|
16
jsvar.d
16
jsvar.d
|
@ -2239,13 +2239,13 @@ WrappedNativeObject wrapNativeObject(Class, bool special = false)(Class obj) if(
|
|||
Parameters!(__traits(getOverloads, Class, memberName)[idx]) fargs;
|
||||
|
||||
|
||||
/*
|
||||
enum lol = static_foreach(fargs.length, 1, -1,
|
||||
`__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) : ParamDefault!(__traits(getOverloads, Class, memberName)[idx], `,`)(),`,`)`);
|
||||
*/
|
||||
/*
|
||||
enum lol = static_foreach(fargs.length, 1, -1,
|
||||
`__traits(getOverloads, obj, memberName)[idx](`,``,` < vargs.length ? vargs[`,`].get!(typeof(fargs[`,`])) :
|
||||
typeof(fargs[`,`]).init,`,`)`);
|
||||
*/
|
||||
|
||||
// FIXME: what if there are multiple @scriptable overloads?!
|
||||
// FIXME: what about @properties?
|
||||
|
@ -2401,6 +2401,18 @@ WrappedNativeObject wrapNativeObject(Struct)(Struct* obj) if(is(Struct == struct
|
|||
return null; // FIXME
|
||||
}
|
||||
|
||||
/+
|
||||
_IDX_
|
||||
|
||||
static_foreach(T.length, q{
|
||||
mixin(q{
|
||||
void
|
||||
} ~ __traits(identifier, T[_IDX_]) ~ q{
|
||||
|
||||
}
|
||||
});
|
||||
+/
|
||||
|
||||
private
|
||||
string static_foreach(size_t length, int t_start_idx, int t_end_idx, string[] t...) pure {
|
||||
assert(__ctfe);
|
||||
|
|
8
script.d
8
script.d
|
@ -3436,6 +3436,12 @@ void repl(bool enhanced = false)(var globals) {
|
|||
import std.stdio;
|
||||
auto lines() { return stdin.byLine; }
|
||||
}
|
||||
|
||||
bool exited;
|
||||
if(globals == null)
|
||||
globals = var.emptyObject;
|
||||
globals.exit = () { exited = true; };
|
||||
|
||||
import std.algorithm;
|
||||
auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject();
|
||||
|
||||
|
@ -3445,7 +3451,7 @@ void repl(bool enhanced = false)(var globals) {
|
|||
, "stdin");
|
||||
auto expressions = parseScript(tokens);
|
||||
|
||||
while(!expressions.empty) {
|
||||
while(!exited && !expressions.empty) {
|
||||
try {
|
||||
expressions.popFront;
|
||||
auto expression = expressions.front;
|
||||
|
|
138
simpleaudio.d
138
simpleaudio.d
|
@ -174,20 +174,120 @@ void main() {
|
|||
Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish
|
||||
}
|
||||
|
||||
/++
|
||||
Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better
|
||||
error handling and disposal than the old way.
|
||||
|
||||
DO NOT USE THE `new` OPERATOR ON THIS! Just construct it inline:
|
||||
|
||||
---
|
||||
auto audio = AudioOutputThread(true);
|
||||
audio.beep();
|
||||
---
|
||||
|
||||
History:
|
||||
Added May 9, 2020 to replace the old [AudioPcmOutThread] class
|
||||
that proved pretty difficult to use correctly.
|
||||
+/
|
||||
struct AudioOutputThread {
|
||||
@disable this();
|
||||
|
||||
/++
|
||||
Pass `true` to enable the audio thread. Otherwise, it will
|
||||
just live as a dummy mock object that you should not actually
|
||||
try to use.
|
||||
+/
|
||||
this(bool enable) {
|
||||
if(enable) {
|
||||
impl = new AudioPcmOutThreadImplementation();
|
||||
impl.refcount++;
|
||||
impl.start();
|
||||
impl.waitForInitialization();
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps an internal refcount.
|
||||
this(this) {
|
||||
if(impl)
|
||||
impl.refcount++;
|
||||
}
|
||||
|
||||
/// When the internal refcount reaches zero, it stops the audio and rejoins the thread, throwing any pending exception (yes the dtor can throw! extremely unlikely though).
|
||||
~this() {
|
||||
if(impl) {
|
||||
impl.refcount--;
|
||||
if(impl.refcount == 0) {
|
||||
impl.stop();
|
||||
impl.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/++
|
||||
This allows you to check `if(audio)` to see if it is enabled.
|
||||
+/
|
||||
bool opCast(T : bool)() {
|
||||
return impl !is null;
|
||||
}
|
||||
|
||||
/++
|
||||
Other methods are forwarded to the implementation of type
|
||||
[AudioPcmOutThreadImplementation]. See that for more information
|
||||
on what you can do.
|
||||
|
||||
This opDispatch template will forward all other methods directly
|
||||
to that [AudioPcmOutThreadImplementation] if this is live, otherwise
|
||||
it does nothing.
|
||||
+/
|
||||
template opDispatch(string name) {
|
||||
static if(is(typeof(__traits(getMember, impl, name)) Params == __parameters))
|
||||
auto opDispatch(Params params) {
|
||||
if(impl)
|
||||
return __traits(getMember, impl, name)(params);
|
||||
static if(!is(typeof(return) == void))
|
||||
return typeof(return).init;
|
||||
}
|
||||
else static assert(0);
|
||||
}
|
||||
|
||||
|
||||
/// provides automatic [arsd.jsvar] script wrapping capability. Make sure the
|
||||
/// script also finishes before this goes out of scope or it may end up talking
|
||||
/// to a dead object....
|
||||
auto toArsdJsvar() {
|
||||
return impl;
|
||||
}
|
||||
|
||||
/+
|
||||
alias getImpl this;
|
||||
AudioPcmOutThreadImplementation getImpl() {
|
||||
assert(impl !is null);
|
||||
return impl;
|
||||
}
|
||||
+/
|
||||
private AudioPcmOutThreadImplementation impl;
|
||||
}
|
||||
|
||||
/++
|
||||
Old thread implementation. I decided to deprecate it in favor of [AudioOutputThread] because
|
||||
RAII semantics make it easier to get right at the usage point. See that to go forward.
|
||||
|
||||
History:
|
||||
Deprecated on May 9, 2020.
|
||||
+/
|
||||
deprecated("Use AudioOutputThread instead.") class AudioPcmOutThread {}
|
||||
|
||||
import core.thread;
|
||||
/++
|
||||
Makes an audio thread for you that you can make
|
||||
various sounds on and it will mix them with good
|
||||
enough latency for simple games.
|
||||
|
||||
---
|
||||
auto audio = new AudioPcmOutThread();
|
||||
audio.start();
|
||||
scope(exit) {
|
||||
audio.stop();
|
||||
audio.join();
|
||||
}
|
||||
DO NOT USE THIS DIRECTLY. Instead, access it through
|
||||
[AudioOutputThread].
|
||||
|
||||
---
|
||||
auto audio = AudioOutputThread(true);
|
||||
audio.beep();
|
||||
|
||||
// you need to keep the main program alive long enough
|
||||
|
@ -195,14 +295,25 @@ import core.thread;
|
|||
Thread.sleep(1.seconds);
|
||||
---
|
||||
+/
|
||||
final class AudioPcmOutThread : Thread {
|
||||
///
|
||||
this() {
|
||||
final class AudioPcmOutThreadImplementation : Thread {
|
||||
private this() {
|
||||
this.isDaemon = true;
|
||||
|
||||
super(&run);
|
||||
}
|
||||
|
||||
private int refcount;
|
||||
|
||||
private void waitForInitialization() {
|
||||
shared(AudioOutput*)* ao = cast(shared(AudioOutput*)*) &this.ao;
|
||||
while(isRunning && *ao is null) {
|
||||
Thread.sleep(5.msecs);
|
||||
}
|
||||
|
||||
if(*ao is null)
|
||||
join(); // it couldn't initialize, just rethrow the exception
|
||||
}
|
||||
|
||||
///
|
||||
void pause() {
|
||||
if(ao) {
|
||||
|
@ -225,6 +336,7 @@ final class AudioPcmOutThread : Thread {
|
|||
}
|
||||
|
||||
/// Args in hertz and milliseconds
|
||||
@scriptable
|
||||
void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME) {
|
||||
Sample s;
|
||||
s.operation = 0; // square wave
|
||||
|
@ -235,6 +347,7 @@ final class AudioPcmOutThread : Thread {
|
|||
}
|
||||
|
||||
///
|
||||
@scriptable
|
||||
void noise(int dur = 150, int volume = DEFAULT_VOLUME) {
|
||||
Sample s;
|
||||
s.operation = 1; // noise
|
||||
|
@ -245,6 +358,7 @@ final class AudioPcmOutThread : Thread {
|
|||
}
|
||||
|
||||
///
|
||||
@scriptable
|
||||
void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME) {
|
||||
Sample s;
|
||||
s.operation = 5; // custom
|
||||
|
@ -260,6 +374,7 @@ final class AudioPcmOutThread : Thread {
|
|||
}
|
||||
|
||||
///
|
||||
@scriptable
|
||||
void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME) {
|
||||
Sample s;
|
||||
s.operation = 5; // custom
|
||||
|
@ -463,6 +578,7 @@ final class AudioPcmOutThread : Thread {
|
|||
|
||||
AudioOutput ao = AudioOutput(0);
|
||||
this.ao = &ao;
|
||||
scope(exit) this.ao = null;
|
||||
auto omg = this;
|
||||
ao.fillData = (short[] buffer) {
|
||||
short[BUFFER_SIZE_SHORT] bfr;
|
||||
|
@ -1640,3 +1756,5 @@ extern(Windows):
|
|||
|
||||
uint mciSendStringA(in char*,char*,uint,void*);
|
||||
}
|
||||
|
||||
private enum scriptable = "arsd_jsvar_compatible";
|
||||
|
|
Loading…
Reference in New Issue