more catching up

This commit is contained in:
Adam D. Ruppe 2023-05-24 08:41:16 -04:00
parent e491654b4d
commit 3e01b447f5
9 changed files with 423 additions and 49 deletions

View File

@ -36,7 +36,7 @@ minigui.d now also depends on a new textlayouter.d, bringing its total dependenc
Generally speaking, I am relaxing my dependency policy somewhat to permit a little more code sharing and interoperability throughout the modules. While I will make efforts to maintain some degree of stand-alone functionality, many new features and even some old features may be changed to use the new module. As such, I reserve to right to use core.d from any module from this point forward. You should be prepared to add it to your builds using any arsd component.
Note that arsd.core may require user32.lib on Windows. This is added automatically in most cases, and is a core component so it will be there, but if you see a linker error, this might be why.
Note that arsd.core may require user32.lib and ws2_32.lib on Windows. This is added automatically in most cases, and is a core component so it will be there, but if you see a linker error, this might be why.
I recommend you clone the repo and use `dmd -i` to let the compiler automatically included imported modules. It really is quite nice to use! But, of course, I don't require it and will call out other required cross-module dependencies in the future too.
@ -62,6 +62,20 @@ lld-link: error: undefined symbol: _D4arsd4core21AsyncOperationRequest5startMFZv
Indicates a missing `core.d` in the build.
### Still coming
11.0 focused on getting breaking changes in before the deadline. Some additive features that had to be deferred will be coming in 11.1 and beyond, including, but not limited to:
* simpleaudio synthesis
* game.d reorganization
* minigui drag and drop
* simpledisplay touch
* ssl server for cgi.d
* tui helpers
* database improvements
* i should prolly rewrite the script.d parser someday but maybe that will be a 12.0 thing
* click and drag capture behavior in minigui and the terminal emulator widget in particular
* more dox
## 10.0

207
core.d
View File

@ -1,4 +1,6 @@
/++
Please note: the api and behavior of this module is not externally stable at this time. See the documentation on specific functions.
Shared core functionality including exception helpers, library loader, event loop, and possibly more. Maybe command line processor and uda helper and some basic shared annotation types.
I'll probably move the url, websocket, and ssl stuff in here too as they are often shared. Maybe a small internationalization helper type (a hook for external implementation) and COM helpers too.
@ -41,6 +43,7 @@ version(Windows) {
import core.sys.windows.winsock2;
pragma(lib, "user32");
pragma(lib, "ws2_32");
} else version(linux) {
version=Arsd_core_epoll;
@ -1346,6 +1349,36 @@ class ArsdExceptionBase : object.Exception {
}
}
/++
+/
class InvalidArgumentsException : ArsdExceptionBase {
static struct InvalidArgument {
string name;
string description;
LimitedVariant givenValue;
}
InvalidArgument[] invalidArguments;
this(InvalidArgument[] invalidArguments, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
this.invalidArguments = invalidArguments;
super(functionName, file, line, next);
}
this(string argumentName, string argumentDescription, LimitedVariant givenArgumentValue = LimitedVariant.init, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
this([
InvalidArgument(argumentName, argumentDescription, givenArgumentValue)
], functionName, file, line, next);
}
override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const {
// FIXME: print the details better
foreach(arg; invalidArguments)
sink("invalidArguments[]", arg.name ~ " " ~ arg.description);
}
}
/++
Base class for when you've requested a feature that is not available. It may not be available because it is possible, but not yet implemented, or it might be because it is impossible on your operating system.
+/
@ -2506,6 +2539,25 @@ class AsyncFile : AbstractFile {
}
/++
Reads or writes a file in one call. It might internally yield, but is generally blocking if it returns values. The callback ones depend on the implementation.
Tip: prefer the callback ones. If settings where async is possible, it will do async, and if not, it will sync.
+/
void writeFile(string filename, const(void)[] contents) {
}
/// ditto
string readTextFile(string filename, string fileEncoding = null) {
return null;
}
/// ditto
const(ubyte[]) readBinaryFile(string filename) {
return null;
}
/+
private Class recycleObject(Class, Args...)(Class objectToRecycle, Args args) {
if(objectToRecycle is null)
@ -4868,15 +4920,87 @@ enum ByteOrder {
bigEndian,
}
/++
A class to help write a stream of binary data to some target.
NOT YET FUNCTIONAL
+/
class WritableStream {
/++
+/
this(size_t bufferSize) {
this(new ubyte[](bufferSize));
}
void put(T)() {}
/// ditto
this(ubyte[] buffer) {
this.buffer = buffer;
}
/++
+/
final void put(T)(T value, ByteOrder byteOrder = ByteOrder.irrelevant) {
static if(T.sizeof == 8)
ulong b;
else static if(T.sizeof == 4)
uint b;
else static if(T.sizeof == 2)
ushort b;
else static if(T.sizeof == 1)
ubyte b;
else static assert(0, "unimplemented type, try using just the basic types");
if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1)
throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte");
final switch(byteOrder) {
case ByteOrder.irrelevant:
writeOneByte(b);
break;
case ByteOrder.littleEndian:
foreach(i; 0 .. T.sizeof) {
writeOneByte(b & 0xff);
b >>= 8;
}
break;
case ByteOrder.bigEndian:
int amount = T.sizeof * 8 - 8;
foreach(i; 0 .. T.sizeof) {
writeOneByte((b >> amount) & 0xff);
amount -= 8;
}
break;
}
}
/// ditto
final void put(T : E[], E)(T value, ByteOrder elementByteOrder = ByteOrder.irrelevant) {
foreach(item; value)
put(item, elementByteOrder);
}
/++
Performs a final flush() call, then marks the stream as closed, meaning no further data will be written to it.
+/
void close() {
isClosed_ = true;
}
/++
Writes what is currently in the buffer to the target and waits for the target to accept it.
Please note: if you are subclassing this to go to a different target
+/
void flush() {}
bool isClosed() { return true; }
/++
Returns true if either you closed it or if the receiving end closed their side, indicating they
don't want any more data.
+/
bool isClosed() {
return isClosed_;
}
// hasRoomInBuffer
// canFlush
@ -4884,6 +5008,20 @@ class WritableStream {
// flushImpl
// markFinished / close - tells the other end you're done
private final writeOneByte(ubyte value) {
if(bufferPosition == buffer.length)
flush();
buffer[bufferPosition++] = value;
}
private {
ubyte[] buffer;
int bufferPosition;
bool isClosed_;
}
}
/++
@ -4893,6 +5031,8 @@ class WritableStream {
from a function generating the data on demand (including an input range), from memory, or from a synchronous file.
A stream of heterogeneous types is compatible with input ranges.
It reads binary data.
+/
class ReadableStream {
@ -4900,9 +5040,22 @@ class ReadableStream {
}
T get(T)(ByteOrder byteOrder = ByteOrder.irrelevant) {
/++
Gets data of the specified type `T` off the stream. The byte order of the T on the stream must be specified unless it is irrelevant (e.g. single byte entries).
---
// get an int out of a big endian stream
int i = stream.get!int(ByteOrder.bigEndian);
// get i bytes off the stream
ubyte[] data = stream.get!(ubyte[])(i);
---
+/
final T get(T)(ByteOrder byteOrder = ByteOrder.irrelevant) {
if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1)
throw new ArsdException!"byte order must be specified for a type that is bigger than one byte";
throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte");
// FIXME: what if it is a struct?
while(bufferedLength() < T.sizeof)
waitForAdditionalData();
@ -4939,26 +5092,37 @@ class ReadableStream {
}
}
// if the stream is closed before getting the length or the terminator, should we send partial stuff
// or just throw?
T get(T : E[], E)(size_t length, ByteOrder elementByteOrder = ByteOrder.irrelevant) {
if(byteOrder == ByteOrder.irrelevant && E.sizeof > 1)
throw new ArsdException!"byte order must be specified for a type that is bigger than one byte";
/// ditto
final T get(T : E[], E)(size_t length, ByteOrder elementByteOrder = ByteOrder.irrelevant) {
if(elementByteOrder == ByteOrder.irrelevant && E.sizeof > 1)
throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte");
// if the stream is closed before getting the length or the terminator, should we send partial stuff
// or just throw?
while(bufferedLength() < length * E.sizeof)
waitForAdditionalData();
T ret;
// FIXME
ret.length = length;
if(false && elementByteOrder == ByteOrder.irrelevant) {
// ret[] =
// FIXME: can prolly optimize
} else {
foreach(i; 0 .. length)
ret[i] = get!E(elementByteOrder);
}
return ret;
}
T get(T : E[], E)(scope bool delegate(E e) isTerminatingSentinel, ByteOrder elementByteOrder = ByteOrder.irrelevant) {
/// ditto
final T get(T : E[], E)(scope bool delegate(E e) isTerminatingSentinel, ByteOrder elementByteOrder = ByteOrder.irrelevant) {
if(byteOrder == ByteOrder.irrelevant && E.sizeof > 1)
throw new ArsdException!"byte order must be specified for a type that is bigger than one byte";
throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte");
assert(0, "Not implemented");
}
@ -5037,6 +5201,8 @@ class ReadableStream {
}
}
// FIXME: do a stringstream too
unittest {
auto stream = new ReadableStream();
@ -5051,6 +5217,9 @@ unittest {
ubyte b = stream.get!ubyte;
assert(b == 33);
position = 3;
// ubyte[] c = stream.get!(ubyte[])(3);
// int[] d = stream.get!(int[])(3);
});
fiber.call();
@ -5059,6 +5228,9 @@ unittest {
assert(position == 2);
stream.feedData([33]);
assert(position == 3);
// stream.feedData([1,2,3]);
// stream.feedData([1,2,3,4,1,2,3,4,1,2,3,4]);
}
/++
@ -5501,7 +5673,16 @@ void writeln(T...)(T t) {
} else static if(is(typeof(arg) : long)) {
auto sliced = intToString(arg, buffer[pos .. $]);
pos += sliced.length;
} else static assert(0, "Unsupported type: " ~ T.stringof);
} else static if(is(typeof(arg.toString()) : const char[])) {
auto s = arg.toString();
buffer[pos .. pos + s.length] = s[];
pos += s.length;
} else {
auto s = "<unsupported type>";
buffer[pos .. pos + s.length] = s[];
pos += s.length;
// static assert(0, "Unsupported type: " ~ T.stringof);
}
}
buffer[pos++] = '\n';

View File

@ -12,7 +12,7 @@
"description": "Shared components across other arsd modules",
"targetType": "library",
"importPaths": ["."],
"libs-windows": ["user32"],
"libs-windows": ["user32", "ws2_32"],
"dflags-dmd": ["-mv=arsd.core=$PACKAGE_DIR/core.d"],
"dflags-ldc": ["--mv=arsd.core=$PACKAGE_DIR/core.d"],
"dflags-gdc": ["-fmodule-file=arsd.core=$PACKAGE_DIR/core.d"],

126
game.d
View File

@ -12,10 +12,14 @@
$(PITFALL
I AM NO LONGER HAPPY WITH THIS INTERFACE AND IT WILL CHANGE.
At least, I am going to change the delta time over to drawFrame
for fractional interpolation while keeping the time step fixed.
While arsd 11 included an overhaul (so you might want to fork
an older version if you relied on it, but the transition is worth
it and wasn't too hard for my game), there's still more stuff changing.
If you depend on it the way it is, you'll want to fork.
This is considered unstable as of arsd 11.0 and will not re-stabilize
until some 11.x release to be determined in the future (and then it might
break again in 12.0, but i'll commit to long term stabilization after that
at the latest).
)
@ -118,6 +122,9 @@
In the overview, I mentioned that there's input available through a few means. Among the functions are:
Checking capabilities:
keyboardIsPresent, mouseIsPresent, gamepadIsPresent, joystickIsPresent, touchIsPresent - return true if there's a physical device for this (tho all can be emulated from just keyboard/mouse)
Gamepads, mouse buttons, and keyboards:
wasPressed - returns true if the button was not pressed but became pressed over the update period.
wasReleased - returns true if the button was pressed, but was released over the update period
@ -135,6 +142,8 @@
stopRecordingCharacters - stops recording characters and clears the recording
You might use this for taking input for chat or character name selection.
FIXME: add an on-screen keyboard thing you can use with gamepads too
Mouse and joystick:
startRecordingPath - starts recording paths, each point coming off the operating system is noted with a timestamp relative to when the recording started
getRecordedPath - gets the current recorded path
@ -153,6 +162,7 @@
changeInPosition - returns the change in position since last time you asked
There may also be raw input data available, since this uses arsd.joystick.
Touch-specific:
$(H2 Window control)
@ -383,6 +393,17 @@ class GameScreenBase {
abstract void load();
private bool loaded;
final void ensureLoaded(GameHelperBase game) {
if(!this.loaded) {
// FIXME: unpause the update thread when it is done
synchronized(game) {
if(!this.loaded) {
this.load();
this.loaded = true;
}
}
}
}
}
/+
@ -429,7 +450,7 @@ class GameScreen(Game) : GameScreenBase {
// You are not supposed to call this.
final void setGame(Game game) {
assert(game_ is null);
// assert(game_ is null);
assert(game !is null);
this.game_ = game;
}
@ -547,6 +568,8 @@ public import arsd.simpleaudio;
import std.math;
public import core.time;
import arsd.core;
public import arsd.joystick;
/++
@ -609,14 +632,14 @@ abstract class GameHelperBase {
Previous to August 27, 2022, this took no arguments. It could thus not interpolate frames!
+/
deprecated("Move to void drawFrame(float) in a GameScreen instead") void drawFrame(float interpolateToNextFrame) {
drawFrameInternal(interpolateToNextFrame);
}
final void drawFrameInternal(float interpolateToNextFrame) {
if(currentScreen is null)
return;
if(!currentScreen.loaded) {
// FIXME: unpause the update thread when it is done
currentScreen.load();
currentScreen.loaded = true;
return;
}
currentScreen.ensureLoaded(this);
currentScreen.drawFrame(interpolateToNextFrame);
}
@ -652,7 +675,8 @@ abstract class GameHelperBase {
if(currentScreen is null)
return false;
synchronized(this) {
(cast() this).currentScreen.update();
if(currentScreen.loaded)
(cast() this).currentScreen.update();
bookkeeping();
return false;
}
@ -928,14 +952,16 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
auto drawer = delegate bool() {
if(gameClock is MonoTime.init)
return false; // can't draw uninitialized info
/* // i think this is the same as if delta < 0 below...
auto time = MonoTime.currTime;
if(gameClock + (1000.msecs / targetUpdateRate) < time) {
import std.stdio; writeln("frame skip ", gameClock, " vs ", time);
writeln("frame skip ", gameClock, " vs ", time);
return false; // we're behind on updates, skip this frame
}
*/
if(false && isImmediateUpdate) {
game.drawFrame(0.0);
game.drawFrameInternal(0.0);
isImmediateUpdate = false;
} else {
auto now = MonoTime.currTime - lastUpdate;
@ -943,25 +969,27 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
auto delta = cast(float) ((nextFrame - now).total!"usecs") / cast(float) nextFrame.total!"usecs";
if(delta < 0) {
//writeln("behind ", cast(int)(delta * 100));
return false; // the render is too far ahead of the updater! time to skip frames to let it catch up
}
game.drawFrame(1.0 - delta);
game.drawFrameInternal(1.0 - delta);
}
rframeCounter++;
/+
if(rframeCounter % 60 == 0) {
import std.stdio;
writeln("frame");
}
+/
return true;
};
import core.thread;
import core.volatile;
Thread renderThread;
Thread updateThread;
Thread renderThread; // FIXME: low priority
Thread updateThread; // FIXME: slightly high priority
// shared things to communicate with threads
ubyte exit;
@ -1085,7 +1113,7 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
// FIXME: rate limiting
// FIXME: triple buffer it.
if(changed) {
if(changed && renderThread is null) {
isImmediateUpdate = true;
window.redrawOpenGlSceneSoon();
}
@ -1100,11 +1128,14 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
renderThread = new Thread({
// FIXME: catch exception and inform the parent
int frames = 0;
int skipped = 0;
Duration renderTime;
Duration flipTime;
Duration renderThrottleTime;
MonoTime initial = MonoTime.currTime;
while(!volatileLoad(&exit)) {
MonoTime start = MonoTime.currTime;
{
@ -1120,6 +1151,7 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
actuallyDrew = drawer();
MonoTime end = MonoTime.currTime;
if(actuallyDrew) {
window.mtLock();
scope(exit)
@ -1133,6 +1165,15 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
glFinish();
clearOpenGlScreen(window);
}
// this is just to wake up the UI thread to check X events again
// (any custom event will force a check of XPending) just cuz apparently
// the readiness of the file descriptor can be reset by one of the vsync functions
static if(UsingSimpledisplayX11) {
__gshared thing = new Object;
window.postEvent(thing);
}
MonoTime flip = MonoTime.currTime;
renderTime += end - start;
@ -1142,14 +1183,20 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
renderThrottleTime += maxRedrawTime - (flip - start);
Thread.sleep(maxRedrawTime - (flip - start));
}
frames++;
//import std.stdio; if(frames % 60 == 0) writeln("frame");
if(actuallyDrew)
frames++;
else
skipped++;
// if(frames % 60 == 0) writeln("frame");
}
import std.stdio;
MonoTime finalt = MonoTime.currTime;
writeln("Average render time: ", renderTime / frames);
writeln("Average flip time: ", flipTime / frames);
writeln("Average throttle time: ", renderThrottleTime / frames);
writeln("Frames: ", frames, ", skipped: ", skipped, " over ", finalt - initial);
});
updateThread = new Thread({
@ -1170,7 +1217,7 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
updateTime += end - start;
frames++;
import std.stdio; if(frames % game.designFps == 0) writeln("update");
// if(frames % game.designFps == 0) writeln("update");
const now = MonoTime.currTime - lastUpdate;
Duration nextFrame = msecs(1000) / targetUpdateRate;
@ -1179,14 +1226,13 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
// falling behind on update...
} else {
waitTime += sleepTime;
// import std.stdio; writeln(sleepTime);
// writeln(sleepTime);
Thread.sleep(sleepTime);
}
}
import std.stdio;
writeln("Average update time:" , updateTime / frames);
writeln("Average wait time:" , waitTime / frames);
writeln("Average update time: " , updateTime / frames);
writeln("Average wait time: " , waitTime / frames);
});
} else {
// single threaded, vsync a bit dangeresque here since it
@ -1218,8 +1264,9 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
// FIXME: set viewport prior to render if width/height changed
window.releaseCurrentOpenGlContext(); // need to let the render thread take it
renderThread.start();
renderThread.priority = Thread.PRIORITY_MIN;
} else {
window.redrawOpenGlScene = { drawer(); };
window.redrawOpenGlScene = { synchronized(game) drawer(); };
renderTimer = new Timer(1000 / 60, { window.redrawOpenGlSceneSoon(); });
}
};
@ -1246,6 +1293,8 @@ void runGame(T : GameHelperBase)(int targetUpdateRate = 0, int maxRedrawRate = 0
}
};
Thread.getThis.priority = Thread.PRIORITY_MAX;
window.eventLoop(0,
delegate (KeyEvent ke) {
game.keyboardState[ke.hardwareCode] = ke.pressed;
@ -1284,6 +1333,7 @@ final class OpenGlTexture {
/// Calls glBindTexture
void bind() {
doLazyLoad();
glBindTexture(GL_TEXTURE_2D, _tex);
}
@ -1294,6 +1344,7 @@ final class OpenGlTexture {
///
void draw(float x, float y, int width = 0, int height = 0, float rotation = 0.0, Color bg = Color.white) {
doLazyLoad();
glPushMatrix();
glTranslatef(x, y, 0);
@ -1325,7 +1376,7 @@ final class OpenGlTexture {
float texCoordHeight() { return _texCoordHeight; } /// ditto
/// Returns the texture ID
uint tex() { return _tex; }
uint tex() { doLazyLoad(); return _tex; }
/// Returns the size of the image
int originalImageWidth() { return _width; }
@ -1349,9 +1400,21 @@ final class OpenGlTexture {
/// Using it when not bound is undefined behavior.
this() {}
private TrueColorImage pendingImage;
private final void doLazyLoad() {
if(pendingImage !is null) {
auto tmp = pendingImage;
pendingImage = null;
bindFrom(tmp);
}
}
/// After you delete it with dispose, you may rebind it to something else with this.
/++
After you delete it with dispose, you may rebind it to something else with this.
If the current thread doesn't own an opengl context, it will save the image to try to lazy load it later.
+/
void bindFrom(TrueColorImage from) {
assert(from !is null);
assert(from.width > 0 && from.height > 0);
@ -1363,6 +1426,11 @@ final class OpenGlTexture {
this.from = from;
if(openGLCurrentContext() is null) {
pendingImage = from;
return;
}
auto _texWidth = _width;
auto _texHeight = _height;

View File

@ -14,6 +14,9 @@
on-demand (meaning it won't be loaded if you don't use it, but if you do
use it, the openssl dynamic libraries must be found in the system search path).
On Windows, you can bundle the openssl dlls with your exe and they will be picked
up when distributed.
You can compile with `-version=without_openssl` to entirely disable ssl support.
http2.d, despite its name, does NOT implement HTTP/2.0, but this

View File

@ -766,6 +766,58 @@ version(Windows) {
WORD wRightMotorSpeed; // high frequency motor. use any value between 0-65535 here
}
struct XINPUT_KEYSTROKE {
WORD VirtualKey;
WCHAR Unicode;
WORD Flags;
BYTE UserIndex;
BYTE HidCode;
}
enum XUSER_INDEX_ANY = 0xff;
enum VK_PAD_A = 0x5800;
enum VK_PAD_B = 0x5801;
enum VK_PAD_X = 0x5802;
enum VK_PAD_Y = 0x5803;
enum VK_PAD_RSHOULDER = 0x5804;
enum VK_PAD_LSHOULDER = 0x5805;
enum VK_PAD_LTRIGGER = 0x5806;
enum VK_PAD_RTRIGGER = 0x5807;
enum VK_PAD_DPAD_UP = 0x5810;
enum VK_PAD_DPAD_DOWN = 0x5811;
enum VK_PAD_DPAD_LEFT = 0x5812;
enum VK_PAD_DPAD_RIGHT = 0x5813;
enum VK_PAD_START = 0x5814;
enum VK_PAD_BACK = 0x5815;
enum VK_PAD_LTHUMB_PRESS = 0x5816;
enum VK_PAD_RTHUMB_PRESS = 0x5817;
enum VK_PAD_LTHUMB_UP = 0x5820;
enum VK_PAD_LTHUMB_DOWN = 0x5821;
enum VK_PAD_LTHUMB_RIGHT = 0x5822;
enum VK_PAD_LTHUMB_LEFT = 0x5823;
enum VK_PAD_LTHUMB_UPLEFT = 0x5824;
enum VK_PAD_LTHUMB_UPRIGHT = 0x5825;
enum VK_PAD_LTHUMB_DOWNRIGHT = 0x5826;
enum VK_PAD_LTHUMB_DOWNLEFT = 0x5827;
enum VK_PAD_RTHUMB_UP = 0x5830;
enum VK_PAD_RTHUMB_DOWN = 0x5831;
enum VK_PAD_RTHUMB_RIGHT = 0x5832;
enum VK_PAD_RTHUMB_LEFT = 0x5833;
enum VK_PAD_RTHUMB_UPLEFT = 0x5834;
enum VK_PAD_RTHUMB_UPRIGHT = 0x5835;
enum VK_PAD_RTHUMB_DOWNRIGHT = 0x5836;
enum VK_PAD_RTHUMB_DOWNLEFT = 0x5837;
enum XINPUT_KEYSTROKE_KEYDOWN = 0x0001;
enum XINPUT_KEYSTROKE_KEYUP = 0x0002;
enum XINPUT_KEYSTROKE_REPEAT = 0x0004;
struct WindowsXInput {
HANDLE dll;
bool loadDll() {
@ -780,6 +832,8 @@ version(Windows) {
XInputGetState = cast(typeof(XInputGetState)) GetProcAddress(dll, "XInputGetState");
XInputSetState = cast(typeof(XInputSetState)) GetProcAddress(dll, "XInputSetState");
XInputGetKeystroke = cast(typeof(XInputGetKeystroke)) GetProcAddress(dll, "XInputGetKeystroke");
return true;
}
@ -798,6 +852,7 @@ version(Windows) {
extern(Windows) {
DWORD function(DWORD, XINPUT_STATE*) XInputGetState;
DWORD function(DWORD, XINPUT_VIBRATION*) XInputSetState;
DWORD function(DWORD, DWORD, XINPUT_KEYSTROKE*) XInputGetKeystroke;
}
// there's other functions but I don't use them; my controllers

View File

@ -385,6 +385,8 @@ extern(C) {
enum LIBSSH2_CHANNEL_WINDOW_DEFAULT = (256*1024);
enum LIBSSH2_CHANNEL_PACKET_DEFAULT = 32768;
int libssh2_session_last_error(LIBSSH2_SESSION *session, char **errmsg, int *errmsg_len, int want_buf);
int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, const char *term, uint term_len, const char *modes, uint modes_len, int width, int height, int width_px, int height_px);
int libssh2_channel_process_startup(

19
png.d
View File

@ -45,17 +45,32 @@ MemoryImage readPngFromBytes(const(ubyte)[] bytes) {
/++
Saves a MemoryImage to a png file. See also: [writeImageToPngFile] which uses memory a little more efficiently
See_Also:
[writePngToArray]
+/
void writePng(string filename, MemoryImage mi) {
// FIXME: it would be nice to write the file lazily so we don't have so many intermediate buffers here
import std.file;
std.file.write(filename, writePngToArray(mi));
}
/++
Creates an in-memory png file from the given memory image, returning it.
History:
Added April 21, 2023 (dub v11.0)
See_Also:
[writePng]
+/
ubyte[] writePngToArray(MemoryImage mi) {
PNG* png;
if(auto p = cast(IndexedImage) mi)
png = pngFromImage(p);
else if(auto p = cast(TrueColorImage) mi)
png = pngFromImage(p);
else assert(0);
import std.file;
std.file.write(filename, writePng(png));
return writePng(png);
}
/++

View File

@ -1,5 +1,14 @@
// https://dpaste.dzfl.pl/7a77355acaec
/+
To share some stuff between two opengl threads:
windows
https://www.khronos.org/opengl/wiki/OpenGL_and_multithreading
linux
https://stackoverflow.com/questions/18879520/sharing-opengl-objects-between-contexts-on-linux
+/
// Search for: FIXME: leaks if multithreaded gc
// https://freedesktop.org/wiki/Specifications/XDND/
@ -1101,6 +1110,13 @@ unittest {
}
}
import arsd.core;
// FIXME: tetris demo
// FIXME: space invaders demo
// FIXME: asteroids demo
/*
version(OSX) {
version=without_opengl;
@ -1515,6 +1531,17 @@ void setOpenGLContextVersion() (ubyte hi, ubyte lo) { sdpyOpenGLContextVersion =
*/
@property bool openGLContextFallbackActivated() () { return (sdpyOpenGLContextVersion == 0); }
/++
History:
Added April 24, 2023 (dub v11.0)
+/
auto openGLCurrentContext() {
version(Windows)
return wglGetCurrentContext();
else
return glXGetCurrentContext();
}
/**
Set window class name for all following `new SimpleWindow()` calls.
@ -3000,6 +3027,9 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
Queues an opengl redraw as soon as the other pending events are cleared.
+/
void redrawOpenGlSceneSoon() {
if(redrawOpenGlScene is null)
return;
if(!redrawOpenGlSceneSoonSet) {
redrawOpenGlSceneEvent = new RedrawOpenGlSceneEvent(this);
this.addEventListener((RedrawOpenGlSceneEvent e) { e.w.redrawOpenGlSceneNow(); });
@ -4281,6 +4311,7 @@ struct EventLoopImpl {
}
throw new Exception("epoll wait failure");
}
// writeln(nfds, " ", events[0].data.fd);
SimpleWindow.processAllCustomEvents(); // anyway
//version(sdddd) { writeln("nfds=", nfds, "; [0]=", events[0].data.fd); }
@ -4334,6 +4365,8 @@ struct EventLoopImpl {
read(customEventFDRead, &n, n.sizeof); // reset counter value to zero again
//{ import core.stdc.stdio; printf("custom event! count=%u\n", eventQueueUsed); }
//SimpleWindow.processAllCustomEvents();
forceXPending = true;
} else {
// some other timer
version(sdddd) { writeln("unknown fd: ", fd); }
@ -10904,7 +10937,8 @@ version(Windows) {
Uint8 mask[]
*/
ubyte[4096] data;
// FIXME: this sux, needs to be dynamic
ubyte[/*4096*/ 256*256*4 + 256*256/8] data;
void fromMemoryImage(MemoryImage mi, out int icon_len, out int width, out int height) {
width = mi.width;
@ -10914,8 +10948,10 @@ version(Windows) {
if(indexedImage is null)
indexedImage = quantize(mi.getAsTrueColorImage());
assert(width %8 == 0); // i don't want padding nor do i want the and mask to get fancy
assert(height %4 == 0);
assert(width <= 256, "image too wide");
assert(height <= 256, "image too tall");
assert(width %8 == 0, "image not multiple of 8 width"); // i don't want padding nor do i want the and mask to get fancy
assert(height %4 == 0, "image not multiple of 4 height");
int icon_plen = height*((width+3)&~3);
int icon_mlen = height*((((width+7)/8)+3)&~3);