├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── README.md ├── demo ├── binview │ ├── binview.d │ ├── dub.sdl │ └── dub.selections.json ├── canvas │ ├── dub.sdl │ ├── dub.selections.json │ └── main.d ├── colorcube │ ├── colorcube.d │ ├── dub.sdl │ └── dub.selections.json ├── drawsprite │ ├── dub.sdl │ ├── dub.selections.json │ └── main.d ├── http │ ├── dub.sdl │ ├── dub.selections.json │ └── httpserve.d ├── inputtiming │ ├── dub.sdl │ ├── dub.selections.json │ └── main.d ├── irc │ ├── dub.sdl │ ├── dub.selections.json │ └── irctest.d ├── libpng │ ├── dub.sdl │ ├── dub.selections.json │ └── pngtobmp.d ├── pewpew │ ├── dub.sdl │ ├── dub.selections.json │ ├── objects.d │ └── pewpew.d ├── portforward │ ├── dub.sdl │ ├── dub.selections.json │ ├── portforward.d │ ├── replay.d │ ├── replayincoming.d │ └── replaystats.d ├── render │ ├── dub.sdl │ ├── dub.selections.json │ └── main.d ├── sqlite │ ├── dub.sdl │ ├── dub.selections.json │ └── exec.d ├── turtle │ ├── dub.sdl │ ├── dub.selections.json │ ├── main.d │ └── turtle.d ├── ui │ ├── dub.sdl │ ├── dub.selections.json │ └── main.d └── x11 │ ├── demo.d │ ├── dub.sdl │ └── dub.selections.json ├── dub.sdl ├── dub.selections.json ├── makejson.sh ├── net ├── asockets.d ├── db │ └── psql │ │ └── package.d ├── dns │ └── dnsbl.d ├── github │ └── rest.d ├── http │ ├── app │ │ └── server.d │ ├── caching.d │ ├── cgi │ │ ├── common.d │ │ └── script.d │ ├── client.d │ ├── common.d │ ├── exception.d │ ├── fastcgi │ │ ├── app.d │ │ └── common.d │ ├── responseex.d │ ├── scgi │ │ └── app.d │ ├── server.d │ └── websocket.d ├── ietf │ ├── headerparse.d │ ├── headers.d │ ├── message.d │ ├── url.d │ └── wrap.d ├── irc │ ├── client.d │ ├── clientreplay.d │ ├── common.d │ └── server.d ├── matrix │ ├── client.d │ └── common.d ├── nntp │ ├── client.d │ └── listener.d ├── oauth │ ├── async.d │ └── common.d ├── shutdown.d ├── smtp │ └── client.d ├── ssl │ ├── openssl.d │ ├── package.d │ └── test.d ├── sync.d └── x11 │ └── package.d ├── sys ├── archive.d ├── benchmark.d ├── btrfs │ ├── clone_range.d │ ├── common.d │ ├── extent_same.d │ └── package.d ├── clipboard.d ├── cmd.d ├── config.d ├── console.d ├── d │ ├── cache.d │ ├── manager.d │ └── repo.d ├── data.d ├── database.d ├── dataio.d ├── datamm.d ├── dataset.d ├── desktop.d ├── file.d ├── gc.d ├── git.d ├── imagemagick.d ├── inotify.d ├── install │ ├── common.d │ ├── dmc.d │ ├── dmd.d │ ├── git.d │ ├── gnuwin32.d │ ├── kindlegen.d │ ├── msys.d │ ├── sevenzip.d │ ├── vs.d │ └── wix.d ├── log.d ├── memory.d ├── net │ ├── ae.d │ ├── cachedcurl.d │ ├── curl.d │ ├── package.d │ ├── system.d │ ├── test.d │ └── wininet.d ├── osrng.d ├── paths.d ├── persistence │ ├── core.d │ ├── json.d │ ├── keyvalue.d │ ├── mapped.d │ ├── memoize.d │ ├── package.d │ └── stringset.d ├── pidfile.d ├── process.d ├── sendinput.d ├── shutdown.d ├── signals.d ├── sqlite3.d ├── term.d ├── timing.d ├── vfs │ ├── curl.d │ ├── net.d │ └── package.d ├── vfs_curl.d └── windows │ ├── dll.d │ ├── exception.d │ ├── imports.d │ ├── input.d │ ├── misc.d │ ├── package.d │ ├── pe │ ├── package.d │ ├── pe.d │ ├── resources.d │ └── versioninfo.d │ ├── process.d │ ├── text.d │ └── window.d ├── testflags.sh ├── ui ├── app │ ├── application.d │ ├── main.d │ ├── posix │ │ └── main.d │ └── windows │ │ └── main.d ├── audio │ ├── audio.d │ ├── mixer │ │ ├── base.d │ │ └── software.d │ ├── sdl2 │ │ └── audio.d │ └── source │ │ ├── base.d │ │ ├── memory.d │ │ └── wave.d ├── shell │ ├── events.d │ ├── sdl2 │ │ └── shell.d │ └── shell.d ├── timer │ ├── sdl2 │ │ └── timer.d │ ├── thread │ │ └── timer.d │ └── timer.d ├── video │ ├── bmfont.d │ ├── notes.md │ ├── renderer.d │ ├── sdl2 │ │ ├── renderer.d │ │ └── video.d │ ├── sdl2common │ │ └── video.d │ ├── software │ │ └── common.d │ ├── threadedvideo.d │ └── video.d └── wm │ ├── application.d │ ├── controls │ └── control.d │ └── notes.txt └── utils ├── aa.d ├── aa_test.d ├── alloc.d ├── appender.d ├── array.d ├── async.d ├── autodata.d ├── bench.d ├── bitmanip.d ├── container ├── hashtable.d ├── list.d ├── listnode.d ├── package.d └── set.d ├── digest.d ├── digest_murmurhash3.d ├── exception.d ├── feed.d ├── fps.d ├── functor ├── algorithm.d ├── composition.d ├── package.d └── primitives.d ├── funopt.d ├── geometry.d ├── graphics ├── bitmap.d ├── color.d ├── draw.d ├── ffmpeg.d ├── fonts │ ├── draw.d │ └── font8x8.d ├── gamma.d ├── gdi.d ├── hls.d ├── im_convert.d ├── image.d ├── libpng.d ├── sdl2image.d ├── sdlimage.d └── view.d ├── gzip.d ├── iconv.d ├── json.d ├── main.d ├── mapset ├── mapset.d ├── package.d ├── vars.d └── visitor.d ├── math ├── combinatorics.d ├── distribution.d ├── longmul.d ├── mixed_radix.d └── package.d ├── meta ├── args.d ├── binding.d ├── binding_v1.d ├── caps.d ├── chain.d ├── misc.d ├── package.d ├── proxy.d ├── rcclass.d ├── reference.d ├── tuplerange.d └── x.d ├── mime.d ├── parallelism.d ├── path.d ├── pred └── algorithm.d ├── promise ├── await.d ├── concurrency.d ├── package.d ├── range.d └── timing.d ├── random.d ├── range.d ├── rangeassoc.d ├── regex.d ├── serialization ├── json.d ├── serialization.d ├── sini.d └── store.d ├── sini.d ├── sound ├── asound.d ├── riff │ ├── common.d │ ├── package.d │ ├── reader.d │ └── writer.d ├── wave.d └── windows.d ├── statequeue.d ├── text ├── ascii.d ├── csv.d ├── functor.d ├── html.d ├── package.d └── parsefp.d ├── textout.d ├── time ├── caldur.d ├── common.d ├── format.d ├── fpdur.d ├── package.d ├── parse.d ├── parsedur.d └── types.d ├── typecons.d ├── vec.d ├── xml ├── common.d ├── entities.d ├── helpers.d ├── lite.d └── package.d ├── xmlbuild.d ├── xmldom.d ├── xmllite.d ├── xmlparser.d ├── xmlrpc.d ├── xmlsel.d ├── xmlwriter.d └── zlib.d /.gitattributes: -------------------------------------------------------------------------------- 1 | *.d whitespace=trailing-space,space-before-tab,indent-with-non-tab,tabwidth=4 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | timeout-minutes: 5 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-22.04, windows-2022, macos-14] 11 | dc: [dmd-2.107.1, ldc-1.32.2] 12 | arch: [x86_64] 13 | include: 14 | # also test an older DMD verrsion 15 | - os: ubuntu-22.04 16 | dc: dmd-2.096.0 17 | arch: x86_64 18 | # also test 32-bit (but only on Windows) 19 | - os: windows-2022 20 | dc: ldc-1.32.2 21 | arch: x86_mscoff # not x86 to avoid OPTLINK deadlock bugs 22 | exclude: 23 | # Do not try to use DMD on Windows 24 | # https://issues.dlang.org/show_bug.cgi?id=22044 25 | - os: windows-2022 26 | dc: dmd-2.107.1 27 | # Do not try to use DMD on macOS 28 | # Fails to link with errors such as: 29 | # "ld: r_symbolnum=20 out of range in '.../libae.a[11668](xmlwriter_2cc8_ad9.o)'" 30 | - os: macos-14 31 | dc: dmd-2.107.1 32 | 33 | runs-on: ${{ matrix.os }} 34 | steps: 35 | - uses: actions/checkout@v2 36 | - name: Install D compiler 37 | uses: dlang-community/setup-dlang@934047eba531212a2c77e7c1b5999d32c2becb81 38 | with: 39 | compiler: ${{ matrix.dc }} 40 | 41 | # https://github.com/dlang-community/setup-dlang/issues/85 42 | - name: Set up PATH on Windows/x86 for LDC libcurl.dll 43 | if: ${{ matrix.os == 'windows-2022' && matrix.dc == 'ldc-1.32.2' && matrix.arch == 'x86_mscoff' }} 44 | run: | 45 | echo "C:\hostedtoolcache\windows\ldc2\1.32.2\x64\ldc2-1.32.2-windows-multilib\lib32" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 46 | 47 | - run: dub test --debug=ae_unittest --arch=${{ matrix.arch }} 48 | 49 | - run: dub test --debug=ae_unittest --arch=${{ matrix.arch }} :sys-net-test 50 | 51 | - if: ${{ matrix.os == 'ubuntu-22.04' }} 52 | run: dub test --debug=ae_unittest --arch=${{ matrix.arch }} :sqlite 53 | 54 | - if: ${{ matrix.os == 'ubuntu-22.04' && matrix.dc == 'dmd-2.107.1' }} # old Dub versions can't fetch packages 55 | run: dub test --debug=ae_unittest --arch=${{ matrix.arch }} :libpng 56 | 57 | - if: ${{ matrix.dc == 'dmd-2.107.1' }} 58 | run: dub test --debug=ae_unittest --arch=${{ matrix.arch }} :windows 59 | 60 | compilation-test: 61 | timeout-minutes: 5 62 | strategy: 63 | matrix: 64 | os: [ubuntu-22.04] 65 | dc: [dmd-2.107.1] 66 | 67 | runs-on: ${{ matrix.os }} 68 | steps: 69 | - uses: actions/checkout@v2 70 | - name: Install D compiler 71 | uses: dlang-community/setup-dlang@934047eba531212a2c77e7c1b5999d32c2becb81 72 | with: 73 | compiler: ${{ matrix.dc }} 74 | 75 | - run: ./makejson.sh 76 | 77 | - shell: bash 78 | run: ./testflags.sh 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.pdb 3 | *.log 4 | *.map 5 | *.html 6 | *.ilk 7 | *.exp 8 | *.lib 9 | 10 | *.[oa] 11 | *.so 12 | *.dll 13 | 14 | # dub 15 | .dub 16 | /ae-test-library 17 | /ae-*-test-lib* 18 | 19 | # demos - rdmd binaries 20 | /demo/binview/binview 21 | /demo/canvas/main 22 | /demo/colorcube/colorcube 23 | /demo/drawsprite/main 24 | /demo/http/httpserve 25 | /demo/inputtiming/main 26 | /demo/irc/irctest 27 | /demo/libpng/pngtobmp 28 | /demo/pewpew/pewpew 29 | /demo/portforward/portforward 30 | /demo/render/main 31 | /demo/sqlite/exec 32 | /demo/turtle/main 33 | /demo/ui/main 34 | /demo/x11/demo 35 | 36 | # demos - dub binaries 37 | /demo/binview/ae-demo-binview 38 | /demo/canvas/ae-demo-canvas 39 | /demo/colorcube/ae-demo-colorcube 40 | /demo/drawsprite/ae-demo-drawsprite 41 | /demo/http/ae-demo-http 42 | /demo/inputtiming/ae-demo-inputtiming 43 | /demo/irc/ae-demo-irc 44 | /demo/libpng/ae-demo-pngtobmp 45 | /demo/pewpew/ae-demo-pewpew 46 | /demo/portforward/ae-demo-portforward 47 | /demo/render/ae-demo-render 48 | /demo/sqlite/ae-demo-sqlite 49 | /demo/turtle/ae-demo-turtle 50 | /demo/ui/ae-demo-ui 51 | /demo/x11/ae-demo-x11 52 | 53 | # generated by makejson.sh 54 | /ae.json 55 | /all.d 56 | -------------------------------------------------------------------------------- /demo/binview/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-binview" 2 | mainSourceFile "binview.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/binview/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "derelict-sdl2": "2.0.2", 6 | "derelict-util": "2.0.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/canvas/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-canvas" 2 | mainSourceFile "main.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main-posix" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/canvas/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "derelict-sdl2": "2.0.2", 6 | "derelict-util": "2.0.4" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/colorcube/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-colorcube" 2 | mainSourceFile "colorcube.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/colorcube/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "derelict-sdl2": "2.0.2", 6 | "derelict-util": "2.0.4" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/drawsprite/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-drawsprite" 2 | mainSourceFile "main.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/drawsprite/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "derelict-sdl2": "2.0.2", 5 | "derelict-util": "2.0.4" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/drawsprite/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.demo.drawsprite.main 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.drawsprite.main; 15 | 16 | import ae.ui.app.application; 17 | import ae.ui.app.main; 18 | import ae.ui.shell.shell; 19 | import ae.ui.shell.sdl2.shell; 20 | import ae.ui.video.sdl2.video; 21 | import ae.ui.video.renderer; 22 | import ae.utils.fps; 23 | import ae.utils.graphics.image; 24 | 25 | final class MyApplication : Application 26 | { 27 | override string getName() { return "Demo/DrawSprite"; } 28 | override string getCompanyName() { return "CyberShadow"; } 29 | 30 | Shell shell; 31 | FPSCounter fps; 32 | Image!BGRX image; 33 | enum SCALE_BASE = 0x10000; 34 | 35 | ImageTextureSource source; 36 | 37 | override void render(Renderer s) 38 | { 39 | fps.tick(&shell.setCaption); 40 | 41 | //auto canvas = s.lock(); 42 | //scope(exit) s.unlock(); 43 | 44 | xy_t x0 = (s.width - image.w) / 2; 45 | xy_t y0 = (s.height - image.h) / 2; 46 | //image.blitTo(canvas, x0, y0); 47 | s.draw(cast(int)x0, cast(int)y0, source, 0, 0, cast(int)image.w, cast(int)image.h); 48 | } 49 | 50 | override int run(string[] args) 51 | { 52 | import std.file : read; 53 | image = read("lena.bmp") 54 | .parseBMP!BGR() 55 | .colorMap!(c => BGRX(c.b, c.g, c.r)) 56 | .copy(); 57 | source = new ImageTextureSource; 58 | source.image = image; 59 | 60 | shell = new SDL2Shell(this); 61 | shell.video = new SDL2Video(); 62 | shell.run(); 63 | shell.video.shutdown(); 64 | return 0; 65 | } 66 | 67 | override void handleQuit() 68 | { 69 | shell.quit(); 70 | } 71 | } 72 | 73 | shared static this() 74 | { 75 | createApplication!MyApplication(); 76 | } 77 | -------------------------------------------------------------------------------- /demo/http/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-http" 2 | mainSourceFile "httpserve.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:openssl" version="*" path="../../" 7 | -------------------------------------------------------------------------------- /demo/http/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "openssl": "3.3.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/http/httpserve.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Start a HTTP server on a port and serve the files in the current directory. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.http.httpserve; 15 | 16 | import std.algorithm.searching; 17 | import std.base64; 18 | import std.conv; 19 | import std.datetime; 20 | import std.exception; 21 | import std.stdio; 22 | import std.string; 23 | 24 | import ae.net.asockets; 25 | import ae.net.http.common; 26 | import ae.net.http.responseex; 27 | import ae.net.http.server; 28 | import ae.net.ietf.headers; 29 | import ae.net.ssl.openssl; 30 | import ae.sys.log; 31 | import ae.sys.shutdown; 32 | import ae.utils.funopt; 33 | import ae.utils.main; 34 | 35 | mixin SSLUseLib; 36 | 37 | void httpserve( 38 | ushort port = 0, string host = null, 39 | string sslCert = null, string sslKey = null, 40 | string userName = null, string password = null, 41 | bool stripQueryParameters = false, 42 | ) 43 | { 44 | HttpServer server; 45 | 46 | if (sslCert || sslKey) 47 | { 48 | auto https = new HttpsServer(); 49 | https.ctx.setCertificate(sslCert); 50 | https.ctx.setPrivateKey(sslKey); 51 | server = https; 52 | } 53 | else 54 | server = new HttpServer(); 55 | 56 | server.log = consoleLogger("Web"); 57 | server.handleRequest = 58 | (HttpRequest request, HttpServerConnection conn) 59 | { 60 | auto response = new HttpResponseEx(); 61 | 62 | if ((userName || password) && 63 | !response.authorize(request, (reqUser, reqPass) => reqUser == userName && reqPass == password)) 64 | return conn.sendResponse(response); 65 | 66 | response.status = HttpStatusCode.OK; 67 | 68 | auto path = request.resource[1..$]; 69 | if (stripQueryParameters) 70 | path = path.findSplit("?")[0]; 71 | 72 | try 73 | response.serveFile( 74 | decodeUrlParameter(path), 75 | "", 76 | true, 77 | formatAddress("http", conn.localAddress, request.host, request.port) ~ "/"); 78 | catch (Exception e) 79 | response.writeError(HttpStatusCode.InternalServerError, e.msg); 80 | conn.sendResponse(response); 81 | }; 82 | server.listen(port, host); 83 | addShutdownHandler((reason) { server.close(); }); 84 | 85 | socketManager.loop(); 86 | } 87 | 88 | mixin main!(funopt!httpserve); 89 | -------------------------------------------------------------------------------- /demo/inputtiming/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-inputtiming" 2 | mainSourceFile "main.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/inputtiming/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "derelict-sdl2": "2.0.2", 6 | "derelict-util": "2.0.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/irc/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-irc" 2 | mainSourceFile "irctest.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | -------------------------------------------------------------------------------- /demo/irc/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /demo/irc/irctest.d: -------------------------------------------------------------------------------- 1 | /** 2 | * IRC client demo. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.irc.irctest; 15 | 16 | import ae.net.asockets; 17 | import ae.net.irc.client; 18 | 19 | void main() 20 | { 21 | auto tcp = new TcpConnection(); 22 | auto c = new IrcClient(tcp); 23 | c.connectNickname = "ae-test"; 24 | c.realname = "https://github.com/CyberShadow/ae"; 25 | c.handleConnect = 26 | { 27 | c.join("#aetest"); 28 | c.message("#aetest", "Test"); 29 | c.disconnect("Bye"); 30 | }; 31 | 32 | tcp.connect("irc.gamesurge.net", 6667); 33 | 34 | socketManager.loop(); 35 | } 36 | -------------------------------------------------------------------------------- /demo/libpng/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-pngtobmp" 2 | mainSourceFile "pngtobmp.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:libpng" version="*" path="../../" 7 | -------------------------------------------------------------------------------- /demo/libpng/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "libpng": "1.6.17" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/libpng/pngtobmp.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.demo.libpng.pngtobmp 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.libpng.pngtobmp; 15 | 16 | import std.exception; 17 | import std.file; 18 | import std.path; 19 | 20 | import ae.utils.funopt; 21 | import ae.utils.graphics.color; 22 | import ae.utils.graphics.image; 23 | import ae.utils.graphics.libpng; 24 | import ae.utils.main; 25 | import ae.utils.meta; 26 | 27 | void pngtobmp(bool strict, bool alpha, bool padding, bool gray, bool rgb, string[] files) 28 | { 29 | enforce(files.length, "No PNG files specified"); 30 | 31 | void cv3(COLOR)() 32 | { 33 | static if (is(typeof(Image!COLOR.init.toBMP))) 34 | { 35 | foreach (fn; files) 36 | { 37 | auto i = decodePNG!COLOR(cast(ubyte[])read(fn), strict); 38 | write(fn.setExtension(".bmp"), i.toBMP); 39 | } 40 | } 41 | else 42 | throw new Exception("Invalid format options"); 43 | } 44 | 45 | void cv1(string[] colorChannels)() 46 | { 47 | if (alpha) 48 | { 49 | enforce(!padding, "Can't use --alpha with --padding"); 50 | cv3!(Color!(ubyte, arrayToTuple!(colorChannels ~ "a")))(); 51 | } 52 | else 53 | if (padding) 54 | cv3!(Color!(ubyte, arrayToTuple!(colorChannels ~ "x")))(); 55 | else 56 | cv3!(Color!(ubyte, arrayToTuple!colorChannels))(); 57 | } 58 | 59 | if (gray) 60 | { 61 | enforce(!rgb, "--rgb meaningless with --gray"); 62 | cv1!(["l"])(); 63 | } 64 | else 65 | if (rgb) 66 | cv1!(["r", "g", "b"])(); 67 | else 68 | cv1!(["b", "g", "r"])(); 69 | } 70 | 71 | mixin main!(funopt!pngtobmp); 72 | -------------------------------------------------------------------------------- /demo/pewpew/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-pewpew" 2 | mainSourceFile "pewpew.d" 3 | sourceFiles "objects.d" 4 | targetType "executable" 5 | 6 | dependency "ae" version="*" path="../../" 7 | dependency "ae:sdl2" version="*" path="../../" 8 | dependency "ae:app-main" version="*" path="../../" 9 | -------------------------------------------------------------------------------- /demo/pewpew/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "derelict-sdl2": "2.0.2", 6 | "derelict-util": "2.0.4" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /demo/portforward/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-portforward" 2 | mainSourceFile "portforward.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | -------------------------------------------------------------------------------- /demo/portforward/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": {} 4 | } 5 | -------------------------------------------------------------------------------- /demo/portforward/replayincoming.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Read a PortForward replay log and answer to inbound connections with recorded data. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.portforward.replayincoming; 15 | 16 | import ae.demo.portforward.replay; 17 | import ae.net.asockets; 18 | import ae.sys.timing; 19 | import ae.sys.log; 20 | 21 | import std.datetime : SysTime, Duration, Clock; 22 | import std.string : format; 23 | import std.getopt; 24 | 25 | Logger log; 26 | 27 | class InboundReplayer : Replayer 28 | { 29 | this(string fn) 30 | { 31 | super(fn); 32 | } 33 | 34 | protected: 35 | override bool handleListen(SysTime time, ushort port) 36 | { 37 | listeners[port] = new Listener(port); 38 | log(format("Listening on port %d", port)); 39 | return true; 40 | } 41 | 42 | override bool handleAccept(SysTime time, uint index, ushort port) 43 | { 44 | listeners[port].s.handleAccept = (TcpConnection s) { onSocketAccept(s, time, index); }; 45 | log(format("Waiting for connection %d on port %d", index, port)); 46 | return false; 47 | } 48 | 49 | private void onSocketAccept(TcpConnection s, SysTime time, uint index) 50 | { 51 | log(format("Accepted connection %d from %s", index, s.remoteAddress())); 52 | auto c = new Connection; 53 | c.s = s; 54 | c.recordStart = time; 55 | c.playStart = Clock.currTime(); 56 | connections[index] = c; 57 | 58 | nextLine(); 59 | } 60 | 61 | override bool handleOutgoingData(SysTime time, uint index, void[] data) 62 | { 63 | log(format("Sending %d bytes of data to connection %d", data.length, index)); 64 | connections[index].at(time, { sendData(index, data); }); 65 | return false; 66 | } 67 | 68 | private void sendData(uint index, void[] data) 69 | { 70 | connections[index].s.send(Data(data)); 71 | nextLine(); 72 | } 73 | 74 | override bool handleOutgoingDisconnect(SysTime time, uint index, string reason) 75 | { 76 | connections[index].at(time, { sendDisconnect(index); }); 77 | return false; 78 | } 79 | 80 | private void sendDisconnect(uint index) 81 | { 82 | connections[index].s.disconnect("Record"); 83 | nextLine(); 84 | } 85 | 86 | private: 87 | Listener[ushort] listeners; 88 | 89 | class Listener 90 | { 91 | TcpServer s; 92 | 93 | this(ushort port) 94 | { 95 | s = new TcpServer(); 96 | s.listen(port); 97 | } 98 | } 99 | 100 | Connection[uint] connections; 101 | 102 | class Connection 103 | { 104 | TcpConnection s; 105 | SysTime recordStart, playStart; 106 | 107 | void at(SysTime recordTime, void delegate() fn) 108 | { 109 | SysTime playTime = playStart + (recordTime - recordStart); 110 | setTimeout(fn, playTime - Clock.currTime()); 111 | } 112 | } 113 | } 114 | 115 | void main(string[] args) 116 | { 117 | bool quiet = false; 118 | getopt(args, std.getopt.config.bundling, 119 | "q|quiet", &quiet); 120 | log = quiet ? new FileLogger("PortForwardReplayIncoming") : new FileAndConsoleLogger("PortForwardReplayIncoming"); 121 | new InboundReplayer(args[1]); 122 | socketManager.loop(); 123 | } 124 | -------------------------------------------------------------------------------- /demo/portforward/replaystats.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Read a PortForward replay log and output a CSV suitable for creating a graph of the data rate. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.portforward.replaystats; 15 | 16 | import ae.demo.portforward.replay; 17 | 18 | import std.datetime : SysTime; 19 | import std.stdio; 20 | 21 | class StatsReplayer : Replayer 22 | { 23 | this(string fn) 24 | { 25 | super(fn); 26 | } 27 | 28 | protected: 29 | ulong total; 30 | 31 | override bool handleOutgoingData(SysTime time, uint index, void[] data) 32 | { 33 | total += data.length; 34 | writefln("%d,%d", time.stdTime, total); 35 | return true; 36 | } 37 | } 38 | 39 | void main(string[] args) 40 | { 41 | new StatsReplayer(args[1]); 42 | } 43 | -------------------------------------------------------------------------------- /demo/render/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-render" 2 | mainSourceFile "main.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main-posix" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/render/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "derelict-sdl2": "2.0.2", 5 | "derelict-util": "2.0.4" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/sqlite/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-sqlite" 2 | mainSourceFile "exec.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sqlite" version="*" path="../../" 7 | -------------------------------------------------------------------------------- /demo/sqlite/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": {} 4 | } 5 | -------------------------------------------------------------------------------- /demo/sqlite/exec.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.demo.sqlite.exec 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.sqlite.exec; 15 | 16 | import std.algorithm; 17 | import std.array; 18 | import std.conv; 19 | import std.datetime.stopwatch; 20 | import std.stdio; 21 | 22 | import ae.sys.sqlite3; 23 | import ae.sys.console; 24 | import ae.utils.text; 25 | 26 | void main(string[] args) 27 | { 28 | if (args.length < 2 || args.length > 3) 29 | return stderr.writeln("Usage: exec DATABASE [COMMAND]"); 30 | 31 | auto db = new SQLite(args[1]); 32 | 33 | void exec(string sql, bool interactive) 34 | { 35 | int idx = 0; 36 | string[][] rows; 37 | StopWatch sw; 38 | sw.start(); 39 | foreach (cells, columns; db.query(sql)) 40 | { 41 | sw.stop(); 42 | if (rows is null) 43 | rows ~= ["#"] ~ array(map!`a.idup`(columns)); 44 | rows ~= [to!string(idx++)] ~ array(map!`a.idup`(cells)); 45 | sw.start(); 46 | } 47 | sw.stop(); 48 | stderr.writeln("Query executed in ", sw.peek().toString().replace("μ", "u")); 49 | 50 | if (rows.length == 0) 51 | return; 52 | auto widths = new size_t[rows[0].length]; 53 | foreach (row; rows) 54 | { 55 | assert(row.length == rows[0].length); 56 | foreach (i, cell; row) 57 | widths[i] = widths[i].max(cell.textWidth()); 58 | } 59 | foreach (j, row; rows) 60 | { 61 | auto rowLines = row.map!splitAsciiLines().array(); 62 | auto lineCount = rowLines.map!(line => line.length).fold!max(size_t(0)); 63 | 64 | foreach (line; 0..lineCount) 65 | { 66 | foreach (i, lines; rowLines) 67 | { 68 | if (i) write(" │ "); 69 | string col = line < lines.length ? lines[line] : null; 70 | write(col, std.array.replicate(" ", widths[i] - std.utf.count(col))); 71 | } 72 | writeln(); 73 | } 74 | if (j==0) 75 | { 76 | foreach (i, w; widths) 77 | { 78 | if (i) write("─┼─"); 79 | write(std.array.replicate("─", w)); 80 | } 81 | writeln(); 82 | } 83 | } 84 | writeln(); 85 | } 86 | 87 | if (args.length == 3) 88 | exec(args[2], false); 89 | else 90 | while (!stdin.eof()) 91 | { 92 | write("sqlite> "); 93 | stdout.flush(); 94 | try 95 | exec(readln().chomp(), true); 96 | catch (Exception e) 97 | writeln(e.msg); 98 | } 99 | } 100 | 101 | import std.utf; 102 | import std.string; 103 | 104 | size_t textWidth(string s) nothrow 105 | { 106 | return s 107 | .splitAsciiLines() 108 | .map!(std.utf.count) 109 | .fold!max(size_t(0)); 110 | } 111 | -------------------------------------------------------------------------------- /demo/turtle/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-turtle" 2 | mainSourceFile "main.d" 3 | sourceFiles "turtle.d" 4 | targetType "executable" 5 | 6 | dependency "ae" version="*" path="../../" 7 | dependency "ae:sdl2" version="*" path="../../" 8 | dependency "ae:app-main" version="*" path="../../" 9 | -------------------------------------------------------------------------------- /demo/turtle/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "derelict-sdl2": "2.0.2", 5 | "derelict-util": "2.0.4" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/turtle/turtle.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple turtle graphics API 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.turtle.turtle; 15 | 16 | import std.math; 17 | 18 | import ae.utils.graphics.draw; 19 | import ae.utils.graphics.view; 20 | 21 | struct Turtle(View) 22 | if (isWritableView!View) 23 | { 24 | alias Color = ViewColor!View; 25 | 26 | /// View to draw on. This will usually be a reference. 27 | View view; 28 | 29 | /// How many pixels is one unit (as used by `forward`). 30 | float scale; 31 | 32 | /// Current turtle coordinates. 33 | float x = 0, y = 0; 34 | 35 | /// Current turtle orientation (in degrees). 36 | /// Initially, the turtle is facing upwards. 37 | float orientation = 270; 38 | 39 | /// Current turtle color. 40 | Color color; 41 | 42 | /// Is the pen currently down? 43 | bool penActive = false; 44 | 45 | /// Put the pen up or down. 46 | void penUp () { penActive = false; } 47 | void penDown() { penActive = true; } /// ditto 48 | 49 | /// Change the turtle orientation by the given number of degrees. 50 | void turnLeft (float deg) { orientation -= deg; } 51 | void turnRight(float deg) { orientation += deg; } /// ditto 52 | void turnAround() { orientation += 180; } 53 | 54 | /// Move the turtle forward by the given number of units. 55 | /// If the pen is down, draw a line with the configured color. 56 | void forward(float distance) 57 | { 58 | // Angle in radians. 59 | float rad = orientation / 180 * PI; 60 | 61 | // Convert distance from units to pixels. 62 | distance *= scale; 63 | 64 | // Endpoint coordinates. 65 | float x0 = this.x; 66 | float y0 = this.y; 67 | float x1 = x0 + distance * cos(rad); 68 | float y1 = y0 + distance * sin(rad); 69 | 70 | // Draw a line if needed. 71 | if (penActive) 72 | view.aaLine(x0, y0, x1, y1, color); 73 | 74 | // Update coordinates. 75 | this.x = x1; 76 | this.y = y1; 77 | } 78 | } 79 | 80 | auto turtle(View)(ref View view) 81 | if (isWritableView!View) 82 | { 83 | import std.typecons; 84 | alias R = NullableRef!View; 85 | R r = R(&view); 86 | return Turtle!R(r); 87 | } 88 | -------------------------------------------------------------------------------- /demo/ui/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-ui" 2 | mainSourceFile "main.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:sdl2" version="*" path="../../" 7 | dependency "ae:app-main" version="*" path="../../" 8 | -------------------------------------------------------------------------------- /demo/ui/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "derelict-sdl2": "2.0.2", 5 | "derelict-util": "2.0.4" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /demo/ui/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.demo.test.main 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.demo.ui.main; 15 | 16 | import std.conv; 17 | 18 | import ae.ui.app.application; 19 | import ae.ui.app.posix.main; 20 | import ae.ui.shell.shell; 21 | import ae.ui.shell.sdl2.shell; 22 | import ae.ui.video.renderer; 23 | import ae.ui.video.sdl2.video; 24 | import ae.ui.video.video; 25 | import ae.ui.wm.application; 26 | import ae.ui.wm.controls.control; 27 | 28 | final class MyApplication : WMApplication 29 | { 30 | override string getName() { return "Demo/UI"; } 31 | override string getCompanyName() { return "CyberShadow"; } 32 | 33 | override int run(string[] args) 34 | { 35 | shell = new SDL2Shell(this); 36 | shell.video = new SDL2SoftwareVideo(); 37 | root.addChild(createView()); 38 | shell.run(); 39 | shell.video.shutdown(); 40 | return 0; 41 | } 42 | 43 | Control createView() 44 | { 45 | return (new Table(2, 2)) 46 | .addChild((new Pad(10.px, 5.percent)).addChild(new SetBGColor!(PaintControl, 0x002222))) 47 | .addChild((new Pad(10.px, 5.percent)).addChild(new SetBGColor!(PaintControl, 0x220022))) 48 | .addChild((new Pad(10.px, 5.percent)).addChild(new SetBGColor!(PaintControl, 0x222200))) 49 | .addChild((new Pad(10.px, 5.percent)).addChild(new SetBGColor!(PaintControl, 0x002200))) 50 | ; 51 | } 52 | } 53 | 54 | shared static this() 55 | { 56 | createApplication!MyApplication(); 57 | } 58 | 59 | // *************************************************************************** 60 | 61 | import ae.utils.meta; 62 | 63 | /// Subclass any control to give it an arbitrary background color. 64 | class SetBGColor(BASE : Control, uint C) : BASE 65 | { 66 | mixin GenerateConstructorProxies; 67 | 68 | enum BGRX color = BGRX(C&0xFF, (C>>8)&0xFF, C>>16); 69 | 70 | override void render(Renderer s, int x, int y) 71 | { 72 | s.fillRect(x, y, x+w, y+h, color); 73 | super.render(s, x, y); 74 | } 75 | } 76 | 77 | // *************************************************************************** 78 | 79 | class PaintControl : Control 80 | { 81 | override void arrange(int rw, int rh) { w = rw; h = rh; } 82 | 83 | struct Coord { uint x, y; BGRX c; void* dummy; } 84 | Coord[] coords; 85 | 86 | override void handleMouseMove(int x, int y, MouseButtons buttons) 87 | { 88 | if (buttons) 89 | { 90 | uint b = cast(uint)buttons; 91 | ubyte channel(ubyte m) { return ((b>>m)&1) ? 0xFF : 0; } 92 | coords ~= Coord(x, y, BGRX(channel(2), channel(1), channel(0))); 93 | } 94 | } 95 | 96 | override void render(Renderer s, int x, int y) 97 | { 98 | //foreach (i; 0..100) 99 | // coords ~= Coord(uniform(0, w), uniform(0, h), uniform(0, 0x1_00_00_00)); 100 | static size_t oldCoordsLength; 101 | if (coords.length != oldCoordsLength) 102 | { 103 | //shell.setCaption(to!string(coords.length)); 104 | oldCoordsLength = coords.length; 105 | } 106 | 107 | // if (coords.length > 100) throw new Exception("derp"); 108 | 109 | auto b = s.lock(); 110 | scope(exit) s.unlock(); 111 | foreach (coord; coords) 112 | if (coord.x < w && coord.y < h) 113 | b[x+coord.x, y+coord.y] = coord.c; 114 | //s.unlock(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /demo/x11/dub.sdl: -------------------------------------------------------------------------------- 1 | name "ae-demo-x11" 2 | mainSourceFile "demo.d" 3 | targetType "executable" 4 | 5 | dependency "ae" version="*" path="../../" 6 | dependency "ae:x11" version="*" path="../../" 7 | -------------------------------------------------------------------------------- /demo/x11/dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "ae": {"path":"../../"}, 5 | "libx11": "0.0.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "derelict-sdl2": "2.0.2", 5 | "derelict-util": "2.0.6", 6 | "libpng": "1.6.17", 7 | "libx11": "0.0.1", 8 | "openssl": "3.3.3", 9 | "win32": "2.107.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /makejson.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generate a JSON file usable with DAutoFix. 5 | # Also create an all.d file, which can be used to run all unit tests. 6 | 7 | IFS=$'\n' 8 | 9 | packages=( 10 | # These don't have any special *compile-time* dependencies. 11 | # (We're not going to try to link the output.) 12 | ae 13 | ae:zlib 14 | ae:sqlite 15 | ) 16 | 17 | rm -f files.txt 18 | for package in "${packages[@]}" 19 | do 20 | dub describe "$package" > describe.json 21 | jq -r --arg package "$package" '.packages[] | select(.name == $package) | .files[] | select(.role == "source") | .path' describe.json >> files.txt 22 | done 23 | mapfile -t files < files.txt 24 | rm files.txt describe.json 25 | 26 | dmd -o- -dw -I.. -Xfae.json "${files[@]}" 27 | 28 | ( 29 | echo "deprecated module ae.all;" 30 | printf '%s\n' "${files[@]}" | sed 's#^\(.*\)\.d$#import ae/\1;#g' | grep -v "package;" | sed s#/#.#g 31 | ) > all.d 32 | -------------------------------------------------------------------------------- /net/http/exception.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Allows throwing exceptions with HTTP status codes. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.net.http.exception; 15 | 16 | import std.exception; 17 | 18 | import ae.net.http.common; 19 | 20 | /// Encapsulates an HTTP status as a D exception. 21 | class HttpException : Exception 22 | { 23 | /// The HTTP status code to return. 24 | HttpStatusCode status; 25 | 26 | this(HttpStatusCode status, string msg = null) 27 | { 28 | this.status = status; 29 | super(msg); 30 | } /// 31 | } 32 | 33 | /// 34 | debug(ae_unittest) unittest 35 | { 36 | import ae.net.http.responseex : HttpResponseEx; 37 | auto response = new HttpResponseEx; 38 | bool evilDetected = true; 39 | try 40 | { 41 | if (evilDetected) 42 | throw new HttpException(HttpStatusCode.Forbidden, "Do no evil!"); 43 | } 44 | catch (HttpException e) 45 | response.writeError(e.status, e.msg); 46 | catch (Exception e) 47 | response.writeError(HttpStatusCode.InternalServerError, e.msg); 48 | } 49 | 50 | /// Throws a corresponding `HttpException` if `val` is false-ish. 51 | T httpEnforce(T)(T val, HttpStatusCode status, string msg = null) 52 | { 53 | return enforce(val, new HttpException(status, msg)); 54 | } 55 | -------------------------------------------------------------------------------- /net/irc/clientreplay.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Replay an IRC session from an IrcClient log file. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Stéphan Kochen 12 | * Vladimir Panteleev 13 | * Vincent Povirk 14 | * Simon Arlott 15 | */ 16 | 17 | module ae.net.irc.clientreplay; 18 | 19 | import ae.net.asockets; 20 | import ae.utils.array : asBytes; 21 | 22 | /// `IConnection` implementation which replays an `IrcClient` log file. 23 | class IrcClientLogSource : IConnection 24 | { 25 | /// `IConnection` stubs. 26 | bool isConnected; 27 | @property ConnectionState state() { return isConnected ? ConnectionState.connected : ConnectionState.disconnected; } /// ditto 28 | 29 | void send(scope Data[] data, int priority) {} /// ditto 30 | alias send = IConnection.send; /// ditto 31 | 32 | void disconnect(string reason = defaultDisconnectReason, DisconnectType type = DisconnectType.requested) {} /// ditto 33 | 34 | @property void handleConnect(ConnectHandler value) { connectHandler = value; } /// ditto 35 | private ConnectHandler connectHandler; 36 | 37 | @property void handleReadData(ReadDataHandler value) 38 | { 39 | readDataHandler = value; 40 | } /// ditto 41 | private ReadDataHandler readDataHandler; 42 | 43 | @property void handleDisconnect(DisconnectHandler value) {} /// ditto 44 | @property void handleBufferFlushed(BufferFlushedHandler value) {} /// ditto 45 | 46 | void recv(Data data) 47 | { 48 | if (readDataHandler) 49 | readDataHandler(data); 50 | } /// ditto 51 | 52 | /// Play this log file. 53 | void run(string fn) 54 | { 55 | import std.algorithm; 56 | import std.stdio; 57 | 58 | isConnected = true; 59 | connectHandler(); 60 | 61 | foreach (line; File(fn).byLine(KeepTerminator.yes)) 62 | { 63 | if (line[0] != '[') 64 | continue; 65 | line = line.findSplit("] ")[2]; 66 | if (line[0] == '<') 67 | recv(Data(line[2..$].asBytes)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /net/matrix/common.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Common Matrix code. Experimental! 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.net.matrix.common; 15 | 16 | import ae.utils.json; 17 | 18 | struct RoomId { string value; } 19 | struct EventId { string value; } 20 | 21 | enum MessageEventType : string 22 | { 23 | none = null, 24 | roomMessage = "m.room.message", 25 | } 26 | 27 | struct RoomMessage 28 | { 29 | JSONFragment fragment; 30 | 31 | this(RoomTextMessage m) { fragment.json = m.toJson(); } 32 | } 33 | 34 | struct RoomTextMessage 35 | { 36 | string body; 37 | string msgtype = "m.text"; 38 | } 39 | -------------------------------------------------------------------------------- /net/nntp/listener.d: -------------------------------------------------------------------------------- 1 | /** 2 | * NNTP listener (periodically poll server for new messages). 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.net.nntp.listener; 15 | 16 | import ae.net.nntp.client; 17 | 18 | import std.datetime; 19 | import std.typecons; 20 | 21 | import ae.sys.timing; 22 | import ae.sys.log; 23 | 24 | /// `NntpListener` polls with this interval. 25 | const POLL_PERIOD = 2.seconds; 26 | 27 | /// NNTP client which polls the server for new messages. 28 | class NntpListener 29 | { 30 | private: 31 | typeof(scoped!NntpClient(Logger.init)) client; 32 | string server; 33 | string lastDate; 34 | bool[string] oldMessages; 35 | TimerTask pollTimer; 36 | bool connected, polling; 37 | int queued; 38 | 39 | void reconnect() 40 | { 41 | assert(!connected); 42 | client.connect(server, &onConnect); 43 | } 44 | 45 | void schedulePoll() 46 | { 47 | pollTimer = setTimeout(&poll, POLL_PERIOD); 48 | } 49 | 50 | void poll() 51 | { 52 | pollTimer = null; 53 | client.getDate(&onDate); 54 | client.getNewNews("*", lastDate[0..8] ~ " " ~ lastDate[8..14] ~ " GMT", &onNewNews); 55 | } 56 | 57 | void onConnect() 58 | { 59 | connected = true; 60 | queued = 0; 61 | 62 | if (polling) 63 | { 64 | if (lastDate) 65 | poll(); 66 | else 67 | client.getDate(&onDate); 68 | } 69 | } 70 | 71 | void onDisconnect(string reason, DisconnectType type) 72 | { 73 | connected = false; 74 | if (polling) 75 | { 76 | if (pollTimer && pollTimer.isWaiting()) 77 | pollTimer.cancel(); 78 | if (type != DisconnectType.requested) 79 | setTimeout(&reconnect, 10.seconds); 80 | } 81 | } 82 | 83 | void onDate(string date) 84 | { 85 | if (polling) 86 | { 87 | if (lastDate is null) 88 | schedulePoll(); 89 | lastDate = date; 90 | } 91 | } 92 | 93 | void onNewNews(string[] reply) 94 | { 95 | auto messages = reply[1..$]; 96 | 97 | assert(queued == 0); 98 | foreach (message; messages) 99 | if (message !in oldMessages) 100 | { 101 | oldMessages[message] = true; 102 | client.getMessage(message, &onMessage); 103 | queued++; 104 | } 105 | if (queued==0) 106 | schedulePoll(); 107 | } 108 | 109 | void onMessage(string[] lines, string num, string id) 110 | { 111 | if (handleMessage) 112 | handleMessage(lines, num, id); 113 | 114 | if (polling) 115 | { 116 | queued--; 117 | if (queued==0) 118 | schedulePoll(); 119 | } 120 | } 121 | 122 | public: 123 | this(Logger log) 124 | { 125 | client = scoped!NntpClient(log); 126 | client.handleDisconnect = &onDisconnect; 127 | } /// 128 | 129 | void connect(string server) 130 | { 131 | this.server = server; 132 | reconnect(); 133 | } /// 134 | 135 | void disconnect() 136 | { 137 | client.disconnect(); 138 | } /// 139 | 140 | void startPolling(string lastDate = null) 141 | { 142 | assert(!polling, "Already polling"); 143 | polling = true; 144 | this.lastDate = lastDate; 145 | if (connected) 146 | poll(); 147 | } /// 148 | 149 | /// Called when NEWNEWS reports a new message. 150 | void delegate(string[] lines, string num, string id) handleMessage; 151 | } 152 | -------------------------------------------------------------------------------- /net/oauth/async.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronous OAuth via ae.net. 3 | * !!! UNFINISHED !!! 4 | * 5 | * License: 6 | * This Source Code Form is subject to the terms of 7 | * the Mozilla Public License, v. 2.0. If a copy of 8 | * the MPL was not distributed with this file, You 9 | * can obtain one at http://mozilla.org/MPL/2.0/. 10 | * 11 | * Authors: 12 | * Vladimir Panteleev 13 | */ 14 | 15 | module ae.net.oauth.async; 16 | 17 | import ae.net.http.common; 18 | 19 | import ae.net.oauth.common; 20 | 21 | void prepareRequest(ref OAuthSession session, HttpRequest request) 22 | { 23 | UrlParameters[] parameters; 24 | parameters ~= request.urlParameters; 25 | if (request.headers.get("Content-Type", "") == "application/x-www-form-urlencoded") 26 | parameters ~= request.decodePostData(); 27 | auto oauthParams = session.prepareRequest(request.baseURL, request.method, parameters); 28 | 29 | request.headers.add("Authorization", oauthHeader(oauthParams)); 30 | 31 | // auto params = request.urlParameters; 32 | // foreach (name, value; oauthParams) 33 | // params.add(name, value); 34 | // request.urlParameters = params; 35 | } 36 | -------------------------------------------------------------------------------- /net/shutdown.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Integration with and wrapper around ae.sys.shutdown 3 | * for networked (ae.net.asockets-based) applications. 4 | * 5 | * Unlike ae.sys.shutdown, the handlers are called from 6 | * within the same thread they were registered from - 7 | * provided that socketManager.loop() is running in that 8 | * thread. 9 | * 10 | * License: 11 | * This Source Code Form is subject to the terms of 12 | * the Mozilla Public License, v. 2.0. If a copy of 13 | * the MPL was not distributed with this file, You 14 | * can obtain one at http://mozilla.org/MPL/2.0/. 15 | * 16 | * Authors: 17 | * Vladimir Panteleev 18 | */ 19 | 20 | // TODO: Unify addShutdownHandler under a common API. 21 | // The host program should decide which shutdown 22 | // driver to use. 23 | 24 | // TODO: Add shuttingDown property 25 | 26 | module ae.net.shutdown; 27 | 28 | /// Register a handler to be called when a shutdown is requested. 29 | /// The handler should close network connections and cancel timers, 30 | /// thus removing all owned resources from the event loop which would 31 | /// block it from exiting cleanly. 32 | void addShutdownHandler(void delegate(scope const(char)[] reason) fn) 33 | { 34 | handlers ~= fn; 35 | if (!registered) 36 | register(); 37 | } 38 | 39 | deprecated void addShutdownHandler(void delegate() fn) 40 | { 41 | addShutdownHandler((scope const(char)[] /*reason*/) { fn(); }); 42 | } /// ditto 43 | 44 | /// Remove a previously-registered handler. 45 | void removeShutdownHandler(void delegate(scope const(char)[] reason) fn) 46 | { 47 | foreach (i, handler; handlers) 48 | if (fn is handler) 49 | { 50 | handlers = handlers[0 .. i] ~ handlers[i+1 .. $]; 51 | return; 52 | } 53 | assert(false, "No such shutdown handler registered"); 54 | } 55 | 56 | /// Calls all registered handlers. 57 | void shutdown(scope const(char)[] reason) 58 | { 59 | foreach_reverse (fn; handlers) 60 | fn(reason); 61 | } 62 | 63 | deprecated void shutdown() 64 | { 65 | shutdown(null); 66 | } 67 | 68 | private: 69 | 70 | static import ae.sys.shutdown; 71 | import std.socket : socketPair; 72 | import ae.net.asockets; 73 | import ae.sys.data; 74 | 75 | // Per-thread 76 | void delegate(scope const(char)[] reason)[] handlers; 77 | bool registered; 78 | 79 | final class ShutdownConnection : TcpConnection 80 | { 81 | Socket pinger; 82 | 83 | this() 84 | { 85 | auto pair = socketPair(); 86 | pair[0].blocking = false; 87 | super(pair[0]); 88 | pinger = pair[1]; 89 | this.handleReadData = &onReadData; 90 | addShutdownHandler(&onShutdown); // for manual shutdown calls 91 | this.daemonRead = true; 92 | } 93 | 94 | void ping(scope const(char)[] reason) //@nogc 95 | { 96 | static immutable ubyte[1] nullReason = [0]; 97 | pinger.send(reason.length ? cast(ubyte[])reason : nullReason[]); 98 | } 99 | 100 | void onShutdown(scope const(char)[] reason) 101 | { 102 | pinger.close(); 103 | } 104 | 105 | void onReadData(Data data) 106 | { 107 | data.asDataOf!char.enter((scope dataBytes) { 108 | auto reason = dataBytes.length == 1 && dataBytes[0] == 0 ? null : dataBytes; 109 | shutdown(reason); 110 | }); 111 | } 112 | } 113 | 114 | void register() 115 | { 116 | registered = true; 117 | auto socket = new ShutdownConnection(); 118 | ae.sys.shutdown.addShutdownHandler(&socket.ping); 119 | } 120 | -------------------------------------------------------------------------------- /sys/benchmark.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Framework code for benchmarking individual functions. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.benchmark; 15 | version(Windows): 16 | 17 | import std.exception; 18 | import core.memory; 19 | 20 | import ae.sys.windows.imports; 21 | mixin(importWin32!q{windows}); 22 | 23 | ulong rdtsc() { asm { rdtsc; } } /// Returns the result of the RDTSC instruction. 24 | 25 | private ulong benchStartTime; 26 | 27 | /// Begin benchmarking. 28 | void benchStart() 29 | { 30 | GC.collect(); 31 | version(DOS) asm { cli; } 32 | benchStartTime = rdtsc(); 33 | } 34 | 35 | /// Finish benchmarking, and return the elapsed ticks. 36 | ulong benchEnd() 37 | { 38 | auto time = rdtsc() - benchStartTime; 39 | version(DOS) asm { sti; } 40 | return time; 41 | } 42 | 43 | static this() 44 | { 45 | try 46 | { 47 | version(DOS) 48 | {} 49 | else 50 | { 51 | HANDLE proc = GetCurrentProcess(); 52 | HANDLE thr = GetCurrentThread(); 53 | 54 | HANDLE tok; 55 | enforce(OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &tok), "OpenProcessToken"); 56 | 57 | LUID luid; 58 | enforce(LookupPrivilegeValue(null, 59 | "SeIncreaseBasePriorityPrivilege", 60 | &luid), "LookupPrivilegeValue"); 61 | 62 | TOKEN_PRIVILEGES tp; 63 | tp.PrivilegeCount = 1; 64 | tp.Privileges[0].Luid = luid; 65 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 66 | 67 | AdjustTokenPrivileges(tok, FALSE, &tp, tp.sizeof, null, null); 68 | enforce(GetLastError() == ERROR_SUCCESS, "AdjustTokenPrivileges"); 69 | 70 | enforce(SetPriorityClass(proc, REALTIME_PRIORITY_CLASS), "SetPriorityClass"); 71 | // enforce(SetPriorityClass(proc, HIGH_PRIORITY_CLASS), "SetPriorityClass"); 72 | enforce(SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL), "SetThreadPriority"); 73 | 74 | enforce(SetProcessAffinityMask(proc, 1), "SetProcessAffinityMask"); 75 | } 76 | } 77 | catch (Exception e) 78 | { 79 | import std.stdio; 80 | writeln("Benchmark initialization error: ", e.msg); 81 | } 82 | 83 | GC.disable(); 84 | } 85 | -------------------------------------------------------------------------------- /sys/btrfs/clone_range.d: -------------------------------------------------------------------------------- 1 | /** 2 | * BTRFS_IOC_CLONE_RANGE. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.btrfs.clone_range; 15 | 16 | version(linux): 17 | 18 | import core.stdc.errno; 19 | import core.sys.posix.sys.ioctl; 20 | 21 | import std.exception; 22 | import std.stdio : File; 23 | 24 | import ae.sys.btrfs.common; 25 | 26 | private: 27 | 28 | enum BTRFS_IOC_CLONE_RANGE = _IOW!btrfs_ioctl_clone_range_args(BTRFS_IOCTL_MAGIC, 13); 29 | 30 | struct btrfs_ioctl_clone_range_args 31 | { 32 | long src_fd; 33 | ulong src_offset, src_length; 34 | ulong dest_offset; 35 | } 36 | 37 | public: 38 | 39 | /// Submit a `BTRFS_IOC_CLONE_RANGE` ioctl. 40 | void cloneRange( 41 | ref const File srcFile, ulong srcOffset, 42 | ref const File dstFile, ulong dstOffset, 43 | ulong length) 44 | { 45 | btrfs_ioctl_clone_range_args args; 46 | 47 | args.src_fd = srcFile.fileno; 48 | args.src_offset = srcOffset; 49 | args.src_length = length; 50 | args.dest_offset = dstOffset; 51 | 52 | int ret = ioctl(dstFile.fileno, BTRFS_IOC_CLONE_RANGE, &args); 53 | errnoEnforce(ret >= 0, "ioctl(BTRFS_IOC_CLONE_RANGE)"); 54 | } 55 | 56 | debug(ae_unittest) unittest 57 | { 58 | if (!checkBtrfs()) 59 | return; 60 | import std.range, std.random, std.algorithm, std.file; 61 | enum blockSize = 16*1024; // TODO: detect 62 | auto data = blockSize.iota.map!(n => uniform!ubyte).array(); 63 | std.file.write("test1.bin", data); 64 | scope(exit) remove("test1.bin"); 65 | auto f1 = File("test1.bin", "rb"); 66 | scope(exit) remove("test2.bin"); 67 | auto f2 = File("test2.bin", "wb"); 68 | cloneRange(f1, 0, f2, 0, blockSize); 69 | f2.close(); 70 | f1.close(); 71 | assert(std.file.read("test2.bin") == data); 72 | } 73 | -------------------------------------------------------------------------------- /sys/btrfs/common.d: -------------------------------------------------------------------------------- 1 | /** 2 | * BTRFS common declarations. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.btrfs.common; 15 | 16 | version(linux): 17 | 18 | package: 19 | 20 | enum BTRFS_IOCTL_MAGIC = 0x94; 21 | 22 | debug (ae_unittest) 23 | { 24 | import ae.sys.file; 25 | import std.stdio : stderr; 26 | 27 | bool checkBtrfs(string moduleName = __MODULE__)() 28 | { 29 | auto fs = getPathFilesystem("."); 30 | if (fs != "btrfs") 31 | { 32 | stderr.writefln("Current filesystem is %s, not btrfs, skipping %s test.", fs, moduleName); 33 | return false; 34 | } 35 | return true; 36 | } 37 | } -------------------------------------------------------------------------------- /sys/btrfs/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * BTRFS package. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.btrfs; 15 | 16 | public import ae.sys.btrfs.extent_same; 17 | public import ae.sys.btrfs.clone_range; 18 | -------------------------------------------------------------------------------- /sys/console.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Enable UTF-8 output on Windows. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.console; 15 | 16 | version(Windows) 17 | { 18 | import core.sys.windows.windows; 19 | private UINT oldCP, oldOutputCP; 20 | 21 | shared static this() 22 | { 23 | oldCP = GetConsoleCP(); 24 | oldOutputCP = GetConsoleOutputCP(); 25 | 26 | SetConsoleCP(65001); 27 | SetConsoleOutputCP(65001); 28 | } 29 | 30 | shared static ~this() 31 | { 32 | SetConsoleCP(oldCP); 33 | SetConsoleOutputCP(oldOutputCP); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sys/datamm.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.datamm 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.datamm; 15 | 16 | import core.stdc.errno; 17 | 18 | import std.exception; 19 | import std.mmfile; 20 | import std.typecons; 21 | 22 | debug(DATA_REFCOUNT) import std.stdio, core.stdc.stdio; 23 | 24 | import ae.sys.data; 25 | 26 | alias MmMode = MmFile.Mode; /// Convenience alias. 27 | 28 | // ************************************************************************ 29 | 30 | /// `Memory` implementation encapsulating a memory-mapped file. 31 | /// When the last reference is released, the file is unmapped. 32 | class MappedMemory : Memory 33 | { 34 | typeof(scoped!MmFile(null)) mmFile; /// The `MmFile` object. 35 | ubyte[] mappedData; /// View of the mapped file data. 36 | 37 | this(string name, MmMode mode, size_t from, size_t to) 38 | { 39 | mmFile = retryInterrupted({ 40 | return scoped!MmFile(name, mode, 0, null); 41 | }); 42 | mappedData = cast(ubyte[])( 43 | (from || to) 44 | ? mmFile.Scoped_payload[from..(to ? to : mmFile.length)] 45 | : mmFile.Scoped_payload[] 46 | ); 47 | 48 | debug(DATA_REFCOUNT) writefln("? -> %s [%s..%s]: Created MappedMemory", cast(void*)this, contents.ptr, contents.ptr + contents.length); 49 | } /// 50 | 51 | debug(DATA_REFCOUNT) 52 | ~this() @nogc 53 | { 54 | printf("? -> %p: Deleted MappedMemory\n", cast(void*)this); 55 | } 56 | 57 | override @property inout(ubyte)[] contents() inout { return mappedData; } /// 58 | override @property size_t size() const { return mappedData.length; } /// 59 | override void setSize(size_t newSize) { assert(false, "Can't resize MappedMemory"); } /// 60 | override @property size_t capacity() const { return mappedData.length; } /// 61 | } 62 | 63 | /// Returns a `Data` viewing a mapped file. 64 | Data mapFile(string name, MmMode mode, size_t from = 0, size_t to = 0) 65 | { 66 | auto memory = unmanagedNew!MappedMemory(name, mode, from, to); 67 | return Data(memory); 68 | } 69 | 70 | private T retryInterrupted(T)(scope T delegate() dg) 71 | { 72 | version (Posix) 73 | { 74 | while (true) 75 | { 76 | try 77 | { 78 | return dg(); 79 | } 80 | catch (ErrnoException e) 81 | { 82 | if (e.errno == EINTR) 83 | continue; 84 | throw e; 85 | } 86 | } 87 | } 88 | else 89 | return dg(); 90 | } 91 | -------------------------------------------------------------------------------- /sys/desktop.d: -------------------------------------------------------------------------------- 1 | /** 2 | * OS-specific desktop stuff. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.desktop; 15 | 16 | // Really just one stray function... though clipboard stuff could go here too 17 | 18 | version (Windows) 19 | { 20 | import ae.sys.windows.imports; 21 | mixin(importWin32!q{winuser}); 22 | 23 | pragma(lib, "user32"); 24 | 25 | /// Get the desktop resolution. 26 | void getDesktopResolution(out uint x, out uint y) 27 | { 28 | x = GetSystemMetrics(SM_CXSCREEN); 29 | y = GetSystemMetrics(SM_CYSCREEN); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sys/gc.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Low-level GC interaction code. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.gc; 15 | 16 | /// Warning: This structure is currently internal, and may change arbitrarily. 17 | struct GCStats 18 | { 19 | size_t poolsize; /// total size of pool 20 | size_t usedsize; /// bytes allocated 21 | size_t freeblocks; /// number of blocks marked FREE 22 | size_t freelistsize; /// total of memory on free lists 23 | size_t pageblocks; /// number of blocks marked PAGE 24 | } 25 | 26 | /// Warning: This function is currently internal, and may change arbitrarily. 27 | extern (C) GCStats gc_stats(); 28 | 29 | /// ditto 30 | void GC_getStats(ref GCStats stats) 31 | { 32 | stats = gc_stats(); 33 | } 34 | -------------------------------------------------------------------------------- /sys/imagemagick.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ImageMagick locator 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.imagemagick; 15 | 16 | /// Obtains the ImageMagick installation path from the Windows registry. 17 | version(Windows) 18 | string imageMagickPath(string value = "BinPath") 19 | { 20 | import std.windows.registry; 21 | return Registry 22 | .localMachine 23 | .getKey(`SOFTWARE\ImageMagick\Current`) 24 | .getValue(value) 25 | .value_SZ; 26 | } 27 | 28 | /// Returns a likely working program name for a given ImageMagick 29 | /// program. 30 | string imageMagickBinary(string program) 31 | { 32 | version(Windows) 33 | { 34 | import std.path; 35 | return buildPath(imageMagickPath(), program ~ ".exe"); 36 | } 37 | else 38 | return program; 39 | } 40 | -------------------------------------------------------------------------------- /sys/install/git.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Git command-line installer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.install.git; 15 | 16 | import std.array; 17 | import std.exception; 18 | import std.file; 19 | import std.path; 20 | import std.process; 21 | 22 | import ae.sys.archive; 23 | import ae.sys.file; 24 | import ae.utils.meta : singleton, I; 25 | 26 | public import ae.sys.install.common; 27 | 28 | /// Installs the git version control system. 29 | class GitInstaller : Installer 30 | { 31 | /// URL to download and install. 32 | string url = "https://github.com/git-for-windows/git/releases/download/v2.21.0.windows.1/PortableGit-2.21.0-32-bit.7z.exe"; 33 | 34 | this() 35 | { 36 | initDigests(); 37 | } 38 | 39 | protected: 40 | @property override string[] requiredExecutables() { return ["git"]; } 41 | @property override string[] binPaths() { return ["cmd"]; } 42 | 43 | override void installImpl(string target) 44 | { 45 | windowsOnly(); 46 | url 47 | .I!save() 48 | .I!unpackTo(target); 49 | } 50 | 51 | static void initDigests() 52 | { 53 | static bool digestsInitialized; 54 | if (digestsInitialized) return; 55 | scope(success) digestsInitialized = true; 56 | 57 | urlDigests["https://github.com/git-for-windows/git/releases/download/v2.21.0.windows.1/PortableGit-2.21.0-32-bit.7z.exe"] = "db083fde82c743a26dbd7fbd597d3a6321522936"; 58 | } 59 | } 60 | 61 | alias gitInstaller = singleton!GitInstaller; /// ditto 62 | -------------------------------------------------------------------------------- /sys/install/gnuwin32.d: -------------------------------------------------------------------------------- 1 | /** 2 | * GnuWin32 installer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.install.gnuwin32; 15 | 16 | import std.file; 17 | import std.path; 18 | import std.string : format, split; 19 | 20 | import ae.utils.meta : singleton, I; 21 | 22 | public import ae.sys.install.common; 23 | 24 | /// Installs a GnuWin32 component. 25 | final class GnuWin32Component : Installer 26 | { 27 | /// Template for component .zip files. 28 | string urlTemplate = "http://gnuwin32.sourceforge.net/downlinks/%s-%s-zip.php"; 29 | 30 | /// Component to install. 31 | string componentName; 32 | 33 | this(string componentName) { this.componentName = componentName; } /// 34 | 35 | protected: 36 | @property override string name() { return "%s (GnuWin32)".format(componentName); } 37 | @property override string subdirectory() { return "gnuwin32"; } 38 | @property override string[] binPaths() { return ["bin"]; } 39 | 40 | override @property bool installedLocally() 41 | { 42 | auto manifestDir = directory.buildPath("manifest"); 43 | return manifestDir.exists && !manifestDir.dirEntries(componentName ~ "-*-bin.ver", SpanMode.shallow).empty; 44 | } 45 | 46 | override void atomicInstallImpl() 47 | { 48 | windowsOnly(); 49 | if (!directory.exists) 50 | directory.mkdir(); 51 | installUrl(urlTemplate.format(componentName, "bin")); 52 | installUrl(urlTemplate.format(componentName, "dep")); 53 | assert(installedLocally); 54 | } 55 | 56 | void installUrl(string url) 57 | { 58 | url 59 | .I!saveAs(url.split("/")[$-1][0..$-8] ~ ".zip") 60 | .I!unpack() 61 | .atomicMoveInto(directory); 62 | } 63 | } 64 | 65 | /// `opDispatch` constructor for GnuWin32 component installers. 66 | struct GnuWin32 67 | { 68 | /// Example: `GnuWin32.make.requireLocal(false);`. 69 | static GnuWin32Component opDispatch(string name)() 70 | { 71 | alias component = singleton!(GnuWin32Component, name); 72 | return component; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sys/install/kindlegen.d: -------------------------------------------------------------------------------- 1 | /** 2 | * KindleGen installer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.install.kindlegen; 15 | 16 | import std.exception; 17 | 18 | import ae.utils.meta : singleton, I; 19 | 20 | public import ae.sys.install.common; 21 | 22 | /// Installs KindleGen, a compiler for Kindle-compatible e-books. 23 | class KindleGenInstaller : Installer 24 | { 25 | /// Download URL. 26 | version (Windows) 27 | enum defaultUrl = "https://dump.cy.md/d4be194f848da73ea09742bc3a787f1b/kindlegen_win32_v2_9.zip"; 28 | else 29 | version (linux) 30 | enum defaultUrl = "https://dump.cy.md/21aef3c8846946203e178c83a37beba1/kindlegen_linux_2.6_i386_v2_9.tar.gz"; 31 | else 32 | version (OSX) 33 | enum defaultUrl = "https://dump.cy.md/204a2a4cc3e95e1a0dbbb9e52a7bc482/KindleGen_Mac_i386_v2_9.zip"; 34 | else 35 | enum defaultUrl = null; 36 | 37 | string url = defaultUrl; /// ditto 38 | 39 | this() 40 | { 41 | initDigests(); 42 | } 43 | 44 | protected: 45 | @property override string[] requiredExecutables() { return ["kindlegen"]; } 46 | 47 | override void installImpl(string target) 48 | { 49 | enforce(url, "KindleGen: No URL or platform not supported"); 50 | url 51 | .I!save() 52 | .I!unpackTo(target); 53 | } 54 | 55 | static void initDigests() 56 | { 57 | static bool digestsInitialized; 58 | if (digestsInitialized) return; 59 | scope(success) digestsInitialized = true; 60 | 61 | urlDigests["https://dump.cy.md/d4be194f848da73ea09742bc3a787f1b/kindlegen_win32_v2_9.zip" ] = "8b5540f12e291b4031ad5197e11c0c9f576ad1e5"; 62 | urlDigests["https://dump.cy.md/21aef3c8846946203e178c83a37beba1/kindlegen_linux_2.6_i386_v2_9.tar.gz"] = "2e46cce099aba2725b5ba9ac9d1b1ecdc9dd77e2"; 63 | urlDigests["https://dump.cy.md/204a2a4cc3e95e1a0dbbb9e52a7bc482/KindleGen_Mac_i386_v2_9.zip" ] = "efa5ad0b05650f7f71543535ea2b232bb5fec571"; 64 | } /// 65 | } 66 | 67 | alias kindleGenInstaller = singleton!KindleGenInstaller; /// ditto 68 | -------------------------------------------------------------------------------- /sys/install/sevenzip.d: -------------------------------------------------------------------------------- 1 | /** 2 | * 7-Zip command-line installer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.install.sevenzip; 15 | 16 | import ae.utils.meta : singleton, I; 17 | 18 | public import ae.sys.install.common; 19 | 20 | /// Installs the 7-Zip archiving tool. 21 | /// Windows-only. 22 | class SevenZipInstaller : Installer 23 | { 24 | string url = "http://downloads.sourceforge.net/sevenzip/7za920.zip"; /// Download URL. 25 | 26 | /// "7za" or "7z", depending on which is available. 27 | @property string exe() 28 | { 29 | if (haveExecutable("7za")) 30 | return "7za"; 31 | else 32 | return "7z"; 33 | } 34 | 35 | this() 36 | { 37 | initDigests(); 38 | } 39 | 40 | protected: 41 | @property override string name() { return "7-Zip"; } 42 | @property override string subdirectory() { return "7z"; } 43 | 44 | @property override string[] requiredExecutables() { assert(false); } 45 | 46 | import ae.utils.path : haveExecutable; 47 | 48 | override void installImpl(string target) 49 | { 50 | windowsOnly(); 51 | url 52 | .I!save() 53 | .I!unpackTo(target); 54 | } 55 | 56 | @property override bool availableOnSystem() 57 | { 58 | return haveExecutable("7z") || haveExecutable("7za"); 59 | } 60 | 61 | static void initDigests() 62 | { 63 | static bool digestsInitialized; 64 | if (digestsInitialized) return; 65 | scope(success) digestsInitialized = true; 66 | 67 | urlDigests["http://downloads.sourceforge.net/sevenzip/7za920.zip"] = "9ce9ce89ebc070fea5d679936f21f9dde25faae0"; 68 | } 69 | } 70 | 71 | alias sevenZip = singleton!SevenZipInstaller; 72 | -------------------------------------------------------------------------------- /sys/install/wix.d: -------------------------------------------------------------------------------- 1 | /** 2 | * WiX Toolset 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.install.wix; 15 | 16 | import std.conv; 17 | import std.file; 18 | import std.regex; 19 | import std.string; 20 | 21 | import ae.sys.archive; 22 | import ae.sys.file; 23 | import ae.utils.meta : singleton, I; 24 | 25 | public import ae.sys.install.common; 26 | 27 | /// Installs the Wix MSI toolkit. 28 | class WixInstaller : Installer 29 | { 30 | string wixVersion = "3.10.4"; /// Version to install. 31 | 32 | protected: 33 | @property override string[] requiredExecutables() { return ["candle", "dark", "heat", "light", "lit", "lux", "melt", "nit", "pyro", "retina", "shine", "smoke", "torch"]; } 34 | 35 | override void installImpl(string target) 36 | { 37 | windowsOnly(); 38 | "https://github.com/wixtoolset/wix3/releases/download/wix%srtm/wix%s-binaries.zip" 39 | .format(wixVersion.split(".").join(), wixVersion.split(".")[0..2].join()) 40 | .I!resolveRedirect() 41 | .I!verify("147ebb26a67c5621a104f9794deae925908884e7") 42 | .I!saveAs("wix-%s.zip".format(wixVersion)) 43 | .I!unpackTo(target); 44 | } 45 | 46 | static string verify(string url, string hash) 47 | { 48 | urlDigests[url] = hash; 49 | return url; 50 | } 51 | } 52 | 53 | alias wixInstaller = singleton!WixInstaller; /// ditto 54 | -------------------------------------------------------------------------------- /sys/memory.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Memory and GC stuff. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.memory; 15 | 16 | import core.exception; 17 | import core.memory; 18 | import core.thread; 19 | 20 | /// Did the GC run since this function's last call on this thread? 21 | /// Not 100% reliable (due to false pointers). 22 | bool gcRan() 23 | { 24 | static bool initialized = false; 25 | static bool destroyed = false; 26 | 27 | static class Beacon 28 | { 29 | ~this() @nogc 30 | { 31 | destroyed = true; 32 | } 33 | } 34 | 35 | if (!initialized) 36 | { 37 | destroyed = false; 38 | new Beacon(); 39 | initialized = true; 40 | } 41 | 42 | bool result = destroyed; 43 | if (destroyed) 44 | { 45 | destroyed = false; 46 | new Beacon(); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | /// Is the given pointer located on the stack of the current thread? 53 | /// Useful to assert on before taking the address of e.g. a struct member. 54 | bool onStack(const(void)* p) 55 | { 56 | auto p0 = thread_stackTop(); 57 | auto p1 = thread_stackBottom(); 58 | return p0 <= p && p <= p1; 59 | } 60 | 61 | debug(ae_unittest) unittest 62 | { 63 | /* .......... */ int l; auto pl = &l; 64 | static /* ... */ int s; auto ps = &s; 65 | static __gshared int g; auto pg = &g; 66 | /* ................. */ auto ph = new int; 67 | assert( pl.onStack()); 68 | assert(!ps.onStack()); 69 | assert(!pg.onStack()); 70 | assert(!ph.onStack()); 71 | } 72 | 73 | private void callExtend() 74 | { 75 | // Call GC.extend with dummy data. 76 | // It will normally exit silently if given a null pointer, 77 | // but not before the reentrance check. 78 | GC.extend(null, 0, 0, null); 79 | } 80 | 81 | /// Asserts that we are not inside a GC collection cycle, 82 | /// by performing a no-op GC operation. 83 | /// If we are, an `InvalidMemoryOperationError` is raised by the runtime. 84 | void assertNotInCollect() @nogc 85 | { 86 | (cast(void function() @nogc)&callExtend)(); 87 | } 88 | 89 | /// Checks if we are inside a GC collection cycle. 90 | /// This is currently done in a dumb and expensive way, so use sparingly. 91 | bool inCollect() @nogc 92 | { 93 | try 94 | assertNotInCollect(); 95 | catch (InvalidMemoryOperationError) 96 | return true; 97 | return false; 98 | } 99 | 100 | debug(ae_unittest) unittest 101 | { 102 | assert(!inCollect()); 103 | 104 | class C 105 | { 106 | static bool tested; 107 | 108 | ~this() @nogc 109 | { 110 | assert(inCollect()); 111 | tested = true; 112 | } 113 | } 114 | 115 | foreach (n; 0..128) 116 | new C; 117 | GC.collect(); 118 | assert(C.tested); 119 | } 120 | -------------------------------------------------------------------------------- /sys/net/ae.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.net implementation using ae.net 3 | * Note: ae.net requires an SSL provider for HTTPS links. 4 | * 5 | * License: 6 | * This Source Code Form is subject to the terms of 7 | * the Mozilla Public License, v. 2.0. If a copy of 8 | * the MPL was not distributed with this file, You 9 | * can obtain one at http://mozilla.org/MPL/2.0/. 10 | * 11 | * Authors: 12 | * Vladimir Panteleev 13 | */ 14 | 15 | module ae.sys.net.ae; 16 | 17 | import ae.net.asockets; 18 | import ae.net.http.client; 19 | import ae.net.ietf.url; 20 | import ae.sys.dataset : DataVec; 21 | import ae.sys.net; 22 | 23 | static import std.file; 24 | 25 | /// `Network` implementation based on `ae.net`. 26 | class AENetwork : Network 27 | { 28 | private Data getData(string url) 29 | { 30 | Data result; 31 | bool got; 32 | 33 | httpGet(url, 34 | (Data data) { result = data; got = true; }, 35 | (string error) { throw new Exception(error); } 36 | ); 37 | 38 | socketManager.loop(); 39 | assert(got); 40 | return result; 41 | } 42 | 43 | override void downloadFile(string url, string target) 44 | { 45 | Data data = getData(url); 46 | data.enter((contents) { 47 | std.file.write(target, contents); 48 | }); 49 | } /// 50 | 51 | override ubyte[] getFile(string url) 52 | { 53 | return getData(url).toGC(); 54 | } /// 55 | 56 | override ubyte[] post(string url, const(ubyte)[] data) 57 | { 58 | Data result; 59 | bool got; 60 | 61 | httpPost(url, DataVec(Data(data)), null, 62 | (Data data) { result = data; got = true; }, 63 | (string error) { throw new Exception(error); } 64 | ); 65 | 66 | socketManager.loop(); 67 | assert(got); 68 | return result.toGC(); 69 | } /// 70 | 71 | override bool urlOK(string url) 72 | { 73 | bool got, result; 74 | 75 | auto request = new HttpRequest; 76 | request.method = "HEAD"; 77 | request.resource = url; 78 | try 79 | { 80 | .httpRequest(request, 81 | (HttpResponse response, string disconnectReason) 82 | { 83 | got = true; 84 | if (!response) 85 | result = false; 86 | else 87 | result = response.status == HttpStatusCode.OK; 88 | } /// 89 | ); 90 | 91 | socketManager.loop(); 92 | } /// 93 | catch (Exception e) 94 | return false; 95 | 96 | assert(got); 97 | return result; 98 | } /// 99 | 100 | override string resolveRedirect(string url) 101 | { 102 | string result; bool got; 103 | 104 | auto request = new HttpRequest; 105 | request.method = "HEAD"; 106 | request.resource = url; 107 | .httpRequest(request, 108 | (HttpResponse response, string disconnectReason) 109 | { 110 | if (!response) 111 | throw new Exception(disconnectReason); 112 | else 113 | { 114 | got = true; 115 | result = response.headers.get("Location", null); 116 | if (result) 117 | result = url.applyRelativeURL(result); 118 | } /// 119 | } /// 120 | ); 121 | 122 | socketManager.loop(); 123 | assert(got); 124 | return result; 125 | } /// 126 | 127 | override HttpResponse httpRequest(HttpRequest request) 128 | { 129 | HttpResponse result; 130 | 131 | .httpRequest(request, 132 | (HttpResponse response, string disconnectReason) 133 | { 134 | if (!response) 135 | throw new Exception(disconnectReason); 136 | else 137 | result = response; 138 | } /// 139 | ); 140 | 141 | socketManager.loop(); 142 | assert(result); 143 | return result; 144 | } /// 145 | } 146 | 147 | static this() 148 | { 149 | net = new AENetwork(); 150 | } 151 | -------------------------------------------------------------------------------- /sys/net/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Abstract interface for basic network operations. 3 | * Import ae.sys.net.* to select an implementation. 4 | * 5 | * License: 6 | * This Source Code Form is subject to the terms of 7 | * the Mozilla Public License, v. 2.0. If a copy of 8 | * the MPL was not distributed with this file, You 9 | * can obtain one at http://mozilla.org/MPL/2.0/. 10 | * 11 | * Authors: 12 | * Vladimir Panteleev 13 | */ 14 | 15 | module ae.sys.net; 16 | 17 | import std.functional; 18 | import std.path; 19 | 20 | import ae.net.http.common; 21 | import ae.net.ietf.url; 22 | import ae.sys.file; 23 | 24 | /// Base interface for basic network operations. 25 | class Network 26 | { 27 | /// Download file located at the indicated URL, 28 | /// unless the target file already exists. 29 | void downloadFile(string url, string target) 30 | { 31 | notImplemented(); 32 | } 33 | 34 | // TODO: use Data instead of ubyte[]? 35 | 36 | /// Get resource located at the indicated URL. 37 | ubyte[] getFile(string url) 38 | { 39 | notImplemented(); 40 | assert(false); 41 | } 42 | 43 | /// Post data to the specified URL. 44 | // TODO: Content-Type? 45 | ubyte[] post(string url, const(ubyte)[] data) 46 | { 47 | notImplemented(); 48 | assert(false); 49 | } 50 | 51 | /// Check if the resource exists and is downloadable. 52 | /// E.g. the HTTP status code for a HEAD request should be 200. 53 | bool urlOK(string url) 54 | { 55 | notImplemented(); 56 | assert(false); 57 | } 58 | 59 | /// Get the destination of an HTTP redirect. 60 | string resolveRedirect(string url) 61 | { 62 | notImplemented(); 63 | assert(false); 64 | } 65 | 66 | /// Perform a HTTP request. 67 | HttpResponse httpRequest(HttpRequest request) 68 | { 69 | notImplemented(); 70 | assert(false); 71 | } 72 | 73 | private final void notImplemented() 74 | { 75 | assert(false, "Not implemented or Network implementation not set"); 76 | } 77 | } 78 | 79 | /// The instance of the selected Network implementation. 80 | Network net; 81 | 82 | static this() 83 | { 84 | assert(!net); 85 | net = new Network(); 86 | } 87 | 88 | /// UFCS-able global synonym functions. 89 | void downloadFile(string url, string target) { net.downloadFile(url, target); } 90 | ubyte[] getFile(string url) { return net.getFile(url); } /// ditto 91 | ubyte[] post(string url, const(ubyte)[] data) { return net.post(url, data); } /// ditto 92 | bool urlOK(string url) { return net.urlOK(url); } /// ditto 93 | string resolveRedirect(string url) { return net.resolveRedirect(url); } /// ditto 94 | -------------------------------------------------------------------------------- /sys/net/system.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Automatically select the Network implementation 3 | * that's most likely to work on the current system. 4 | * 5 | * License: 6 | * This Source Code Form is subject to the terms of 7 | * the Mozilla Public License, v. 2.0. If a copy of 8 | * the MPL was not distributed with this file, You 9 | * can obtain one at http://mozilla.org/MPL/2.0/. 10 | * 11 | * Authors: 12 | * Vladimir Panteleev 13 | */ 14 | 15 | module ae.sys.net.system; 16 | 17 | version(Windows) 18 | { 19 | // ae.sys.windows.dll does not compile on 20 | // 2.066 or earlier due to a compiler bug. 21 | static if (__VERSION__ > 2_066) 22 | import ae.sys.net.wininet; 23 | else 24 | import ae.sys.net.curl; 25 | } 26 | else 27 | { 28 | import ae.sys.net.ae; 29 | import ae.net.ssl.openssl; 30 | mixin SSLUseLib; 31 | } 32 | -------------------------------------------------------------------------------- /sys/osrng.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface to the OS CSPRNG. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.osrng; 15 | 16 | import std.conv : to; 17 | 18 | version (CRuntime_Bionic) 19 | version = SecureARC4Random; // ChaCha20 20 | version (OSX) 21 | version = SecureARC4Random; // AES 22 | version (OpenBSD) 23 | version = SecureARC4Random; // ChaCha20 24 | version (NetBSD) 25 | version = SecureARC4Random; // ChaCha20 26 | 27 | // Not SecureARC4Random: 28 | // CRuntime_UClibc (ARC4) 29 | // FreeBSD (ARC4) 30 | // DragonFlyBSD (ARC4) 31 | 32 | version (Windows) 33 | { 34 | import ae.sys.windows; 35 | 36 | import ae.sys.windows.imports; 37 | mixin(importWin32!q{wincrypt}); 38 | mixin(importWin32!q{windef}); 39 | 40 | /// Fill `buf` with random data. 41 | void genRandom(ubyte[] buf) 42 | { 43 | HCRYPTPROV hCryptProv; 44 | wenforce(CryptAcquireContext(&hCryptProv, null, null, PROV_RSA_FULL, 0), "CryptAcquireContext"); 45 | scope(exit) wenforce(CryptReleaseContext(hCryptProv, 0), "CryptReleaseContext"); 46 | wenforce(CryptGenRandom(hCryptProv, buf.length.to!DWORD, buf.ptr), "CryptGenRandom"); 47 | } 48 | } 49 | else 50 | version (SecureARC4Random) 51 | { 52 | extern(C) private @nogc nothrow 53 | { 54 | void arc4random_buf(scope void* buf, size_t nbytes) @system; 55 | } 56 | 57 | /// Fill `buf` with random data. 58 | void genRandom(ubyte[] buf) 59 | { 60 | arc4random_buf(buf.ptr, buf.length); 61 | } 62 | } 63 | else 64 | version (Posix) 65 | { 66 | import std.stdio; 67 | import std.exception; 68 | 69 | /// Fill `buf` with random data. 70 | void genRandom(ubyte[] buf) 71 | { 72 | auto f = File("/dev/urandom"); 73 | auto result = f.rawRead(buf); 74 | enforce(result.length == buf.length, "Couldn't read enough random bytes"); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /sys/persistence/json.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.persistence.json 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.persistence.json; 15 | 16 | import ae.sys.file : atomicWrite; 17 | import ae.sys.persistence.core; 18 | 19 | // **************************************************************************** 20 | 21 | /// `FileCache` wrapper which stores a D type as JSON serialization. 22 | template JsonFileCache(T, FlushPolicy flushPolicy = FlushPolicy.none) 23 | { 24 | import std.file; 25 | import ae.utils.json; 26 | 27 | static T getJson(T)(string fileName) 28 | { 29 | return fileName.readText.jsonParse!T; 30 | } 31 | 32 | static void putJson(T)(string fileName, in T t) 33 | { 34 | atomicWrite(fileName, t.toJson()); 35 | } 36 | 37 | alias JsonFileCache = FileCache!(getJson!T, putJson!T, flushPolicy); 38 | } 39 | 40 | debug(ae_unittest) unittest 41 | { 42 | import std.file; 43 | 44 | enum FN = "test1.json"; 45 | std.file.write(FN, "{}"); 46 | scope(exit) remove(FN); 47 | 48 | auto cache = JsonFileCache!(string[string])(FN); 49 | assert(cache.length == 0); 50 | } 51 | 52 | debug(ae_unittest) unittest 53 | { 54 | import std.file; 55 | 56 | enum FN = "test2.json"; 57 | scope(exit) if (FN.exists) remove(FN); 58 | 59 | auto cache = JsonFileCache!(string[string], FlushPolicy.manual)(FN); 60 | assert(cache.length == 0); 61 | cache["foo"] = "bar"; 62 | cache.save(); 63 | 64 | auto cache2 = JsonFileCache!(string[string])(FN); 65 | assert(cache2["foo"] == "bar"); 66 | } 67 | 68 | debug(ae_unittest) unittest 69 | { 70 | import std.file; 71 | 72 | enum FN = "test3.json"; 73 | scope(exit) if (FN.exists) remove(FN); 74 | 75 | { 76 | auto cache = JsonFileCache!(string[string], FlushPolicy.atScopeExit)(FN); 77 | cache["foo"] = "bar"; 78 | } 79 | 80 | auto cache2 = JsonFileCache!(string[string])(FN); 81 | assert(cache2["foo"] == "bar"); 82 | } 83 | -------------------------------------------------------------------------------- /sys/persistence/mapped.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.persistence.mapped 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.persistence.mapped; 15 | 16 | import std.file; 17 | import std.mmfile; 18 | import std.typecons; 19 | 20 | /// Map a file onto a D type. 21 | /// Experimental. 22 | struct Mapped(T) 23 | { 24 | this(string fn) 25 | { 26 | if (!fn.exists) 27 | std.file.write(fn, [T.init]); 28 | __mapped_file = __mapped_makeFile(fn); 29 | } /// 30 | 31 | private static auto __mapped_makeFile(string fn) 32 | { 33 | static if (is(typeof({T t = void; t = t;}))) 34 | enum mode = MmFile.Mode.readWrite; 35 | else 36 | enum mode = MmFile.Mode.read; 37 | return scoped!MmFile(fn, mode, T.sizeof, null); 38 | } 39 | 40 | typeof(__mapped_makeFile(null)) __mapped_file; 41 | @disable this(this); 42 | 43 | @property ref T __mapped_data() 44 | { 45 | return *cast(T*)__mapped_file[].ptr; 46 | } 47 | alias __mapped_data this; 48 | } 49 | 50 | /// 51 | debug(ae_unittest) unittest 52 | { 53 | static struct S 54 | { 55 | ubyte value; 56 | } 57 | 58 | enum fn = "test.bin"; 59 | scope(success) remove(fn); 60 | auto m = Mapped!S(fn); 61 | 62 | m.value = 1; 63 | assert(read(fn) == [ubyte(1)]); 64 | version (Posix) 65 | { 66 | write(fn, [ubyte(2)]); 67 | assert(m.value == 2); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sys/persistence/memoize.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.persistence.memoize 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.persistence.memoize; 15 | 16 | import std.traits; 17 | import std.typecons; 18 | 19 | import ae.sys.persistence.core; 20 | import ae.sys.persistence.json; 21 | import ae.utils.json; 22 | 23 | // **************************************************************************** 24 | 25 | // https://issues.dlang.org/show_bug.cgi?id=7016 26 | static import ae.utils.json; 27 | 28 | /// std.functional.memoize variant with automatic persistence 29 | struct PersistentMemoized(alias fun, FlushPolicy flushPolicy = FlushPolicy.atThreadExit) 30 | { 31 | alias _AA = ReturnType!fun[string]; 32 | private JsonFileCache!(_AA, flushPolicy) memo; 33 | 34 | this(string fileName) { memo.fileName = fileName; } /// 35 | 36 | ReturnType!fun opCall(ParameterTypeTuple!fun args) 37 | { 38 | string key; 39 | static if (args.length==1 && is(typeof(args[0]) : string)) 40 | key = args[0]; 41 | else 42 | key = toJson(tuple(args)); 43 | auto p = key in memo; 44 | if (p) return *p; 45 | auto r = fun(args); 46 | return memo[key] = r; 47 | } /// 48 | } 49 | 50 | debug(ae_unittest) unittest 51 | { 52 | import std.file : exists, remove; 53 | 54 | static int value = 42; 55 | int getValue(int x) { return value; } 56 | 57 | enum FN = "test4.json"; 58 | scope(exit) if (FN.exists) remove(FN); 59 | 60 | { 61 | auto getValueMemoized = PersistentMemoized!(getValue, FlushPolicy.atScopeExit)(FN); 62 | 63 | assert(getValueMemoized(1) == 42); 64 | value = 24; 65 | assert(getValueMemoized(1) == 42); 66 | assert(getValueMemoized(2) == 24); 67 | } 68 | 69 | value = 0; 70 | 71 | { 72 | auto getValueMemoized = PersistentMemoized!(getValue, FlushPolicy.atScopeExit)(FN); 73 | assert(getValueMemoized(1) == 42); 74 | assert(getValueMemoized(2) == 24); 75 | } 76 | } 77 | 78 | /// As above, but with synchronization 79 | struct SynchronizedPersistentMemoized(alias fun, FlushPolicy flushPolicy = FlushPolicy.atThreadExit) 80 | { 81 | alias _AA = ReturnType!fun[string]; 82 | private JsonFileCache!(_AA, flushPolicy) memo; 83 | private Object mutex; 84 | 85 | this(string fileName) 86 | { 87 | memo.fileName = fileName; 88 | mutex = new Object; 89 | } /// 90 | 91 | ReturnType!fun opCall(ParameterTypeTuple!fun args) 92 | { 93 | string key; 94 | static if (args.length==1 && is(typeof(args[0]) : string)) 95 | key = args[0]; 96 | else 97 | key = toJson(tuple(args)); 98 | synchronized (mutex) 99 | { 100 | auto p = key in memo; 101 | if (p) return *p; 102 | } 103 | auto r = fun(args); 104 | synchronized (mutex) 105 | return memo[key] = r; 106 | } /// 107 | } 108 | -------------------------------------------------------------------------------- /sys/persistence/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrappers for automatically loading/saving data. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.persistence; 15 | 16 | public import ae.sys.persistence.core; 17 | public import ae.sys.persistence.json; 18 | public import ae.sys.persistence.memoize; 19 | public import ae.sys.persistence.stringset; 20 | -------------------------------------------------------------------------------- /sys/persistence/stringset.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.persistence.stringset 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.persistence.stringset; 15 | 16 | import ae.sys.persistence.core; 17 | 18 | // **************************************************************************** 19 | 20 | // https://issues.dlang.org/show_bug.cgi?id=7016 21 | static import ae.sys.file; 22 | 23 | /// A string hashset, stored one line per entry. 24 | struct PersistentStringSet 25 | { 26 | import ae.utils.aa : HashSet; 27 | 28 | static HashSet!string _load(string fileName) 29 | { 30 | import std.file : readText; 31 | import std.string : splitLines; 32 | 33 | return HashSet!string(fileName.readText().splitLines()); 34 | } /// 35 | 36 | static void _save(string fileName, HashSet!string data) 37 | { 38 | import std.array : join; 39 | import ae.sys.file : atomicWrite; 40 | 41 | atomicWrite(fileName, data.keys.join("\n")); 42 | } /// 43 | 44 | private alias Cache = FileCache!(_load, _save, FlushPolicy.manual); 45 | private Cache cache; 46 | 47 | this(string fileName) { cache = Cache(fileName); } /// 48 | 49 | auto opBinaryRight(string op)(string key) 50 | if (op == "in") 51 | { 52 | return key in cache; 53 | } /// 54 | 55 | void add(string key) 56 | { 57 | assert(key !in cache); 58 | cache.add(key); 59 | cache.save(); 60 | } /// 61 | 62 | void remove(string key) 63 | { 64 | assert(key in cache); 65 | cache.remove(key); 66 | cache.save(); 67 | } /// 68 | 69 | @property string[] lines() { return cache.keys; } /// 70 | @property size_t length() { return cache.length; } /// 71 | } 72 | 73 | /// 74 | debug(ae_unittest) unittest 75 | { 76 | import std.file, std.conv, core.thread; 77 | 78 | enum FN = "test.txt"; 79 | if (FN.exists) remove(FN); 80 | scope(exit) if (FN.exists) remove(FN); 81 | 82 | { 83 | auto s = PersistentStringSet(FN); 84 | assert("foo" !in s); 85 | assert(s.length == 0); 86 | s.add("foo"); 87 | } 88 | { 89 | auto s = PersistentStringSet(FN); 90 | assert("foo" in s); 91 | assert(s.length == 1); 92 | s.remove("foo"); 93 | } 94 | { 95 | auto s = PersistentStringSet(FN); 96 | assert("foo" !in s); 97 | Thread.sleep(filesystemTimestampGranularity); 98 | std.file.write(FN, "foo\n"); 99 | assert("foo" in s); 100 | Thread.sleep(filesystemTimestampGranularity); 101 | std.file.write(FN, "bar\n"); 102 | assert(s.lines == ["bar"], text(s.lines)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /sys/pidfile.d: -------------------------------------------------------------------------------- 1 | /** 2 | * PID file and lock 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.pidfile; 15 | 16 | import std.conv : text; 17 | import std.exception; 18 | import std.file : thisExePath, tempDir; 19 | import std.path : baseName, buildPath; 20 | import std.process : thisProcessID; 21 | import std.stdio : File; 22 | 23 | import ae.sys.process : getCurrentUser; 24 | 25 | version (Posix) import core.sys.posix.unistd : geteuid; 26 | 27 | static File pidFile; /// The PID file. Kept open and locked while the program is running. 28 | 29 | /// Create and lock a PID file. 30 | /// If the PID file exists and is locked, an exception is thrown. 31 | /// To use, simply call this function at the top of `main` to prevent multiple instances. 32 | void createPidFile( 33 | string name = defaultPidFileName(), 34 | string path = defaultPidFilePath()) 35 | { 36 | auto fullPath = buildPath(path, name); 37 | assert(!pidFile.isOpen(), "A PID file has already been created for this program / process"); 38 | pidFile.open(fullPath, "w+b"); 39 | scope(failure) pidFile.close(); 40 | enforce(pidFile.tryLock(), "Failed to acquire lock on PID file " ~ fullPath ~ ". Is another instance running?"); 41 | pidFile.write(thisProcessID); 42 | pidFile.flush(); 43 | } 44 | 45 | /// The default `name` argument for `createPidFile`. 46 | /// Returns a file name containing the username and program name. 47 | string defaultPidFileName() 48 | { 49 | return text(getCurrentUser(), "-", thisExePath.baseName, ".pid"); 50 | } 51 | 52 | /// The default `path` argument for `createPidFile`. 53 | /// Returns "/var/run" if running as root on POSIX, 54 | /// or the temporary directory otherwise. 55 | string defaultPidFilePath() 56 | { 57 | version (Posix) 58 | { 59 | if (geteuid() == 0) 60 | return "/var/run"; 61 | else 62 | return tempDir; // /var/run and /var/lock are usually not writable for non-root processes 63 | } 64 | version (Windows) 65 | return tempDir; 66 | } 67 | 68 | debug(ae_unittest) unittest 69 | { 70 | createPidFile(); 71 | 72 | auto realPidFile = pidFile; 73 | pidFile = File.init; 74 | 75 | static void runForked(void delegate() code) 76 | { 77 | version (Posix) 78 | { 79 | // Since locks are per-process, we cannot test lock failures within 80 | // the same process. fork() is used to create a second process. 81 | import core.sys.posix.sys.wait : wait; 82 | import core.sys.posix.unistd : fork, _exit; 83 | int child, status; 84 | if ((child = fork()) == 0) 85 | { 86 | code(); 87 | _exit(0); 88 | } 89 | else 90 | { 91 | assert(wait(&status) != -1); 92 | assert(status == 0, "Fork crashed"); 93 | } 94 | } 95 | else 96 | code(); 97 | } 98 | 99 | runForked({ 100 | assertThrown!Exception(createPidFile); 101 | assert(!pidFile.isOpen); 102 | }); 103 | 104 | realPidFile.close(); 105 | 106 | runForked({ 107 | createPidFile(); 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /sys/process.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.sys.process 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.process; 15 | 16 | version (Posix) 17 | { 18 | import core.sys.posix.unistd : getlogin; 19 | import std.process : environment; 20 | import std.string : fromStringz; 21 | } 22 | version (Windows) 23 | { 24 | import core.sys.windows.lmcons : UNLEN; 25 | import core.sys.windows.winbase : GetUserNameW; 26 | import core.sys.windows.windef : DWORD; 27 | import core.sys.windows.winnt : WCHAR; 28 | import ae.sys.windows.exception : wenforce; 29 | import ae.sys.windows.text : fromWString; 30 | } 31 | 32 | /// Get the name of the user that the current process is running under. 33 | // Note: Windows does not have numeric user IDs, which is why this 34 | // cross-platform function always returns a string. 35 | string getCurrentUser() 36 | { 37 | version (Posix) 38 | return environment.get("LOGNAME", cast(string)getlogin().fromStringz); 39 | version (Windows) 40 | { 41 | WCHAR[UNLEN + 1] buf; 42 | DWORD len = buf.length; 43 | GetUserNameW(buf.ptr, &len).wenforce("GetUserNameW"); 44 | return buf[].fromWString(); 45 | } 46 | } 47 | 48 | version(Posix): 49 | 50 | import ae.net.sync; 51 | import ae.sys.signals; 52 | 53 | import std.process; 54 | 55 | /// Asynchronously wait for a process to terminate. 56 | void asyncWait(Pid pid, void delegate(int status) dg) 57 | { 58 | auto anchor = new ThreadAnchor; 59 | 60 | void handler() nothrow @nogc 61 | { 62 | anchor.runAsync( 63 | { 64 | // Linux may coalesce multiple SIGCHLD into one, so 65 | // we need to explicitly check if our process exited. 66 | auto result = tryWait(pid); 67 | if (result.terminated) 68 | { 69 | removeSignalHandler(SIGCHLD, &handler); 70 | dg(result.status); 71 | } 72 | }); 73 | } 74 | 75 | addSignalHandler(SIGCHLD, &handler); 76 | } 77 | 78 | debug(ae_unittest) import ae.sys.timing, ae.net.asockets; 79 | 80 | debug(ae_unittest) unittest 81 | { 82 | string order; 83 | 84 | auto pid = spawnProcess(["sleep", "1"]); 85 | asyncWait(pid, (int status) { assert(status == 0); order ~= "b"; }); 86 | setTimeout({ order ~= "a"; }, 500.msecs); 87 | setTimeout({ order ~= "c"; }, 1500.msecs); 88 | socketManager.loop(); 89 | 90 | assert(order == "abc"); 91 | } 92 | -------------------------------------------------------------------------------- /sys/signals.d: -------------------------------------------------------------------------------- 1 | /** 2 | * POSIX signal handlers. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.signals; 15 | version(Posix): 16 | 17 | import std.exception; 18 | 19 | public import core.sys.posix.signal; 20 | 21 | /// Handler callback type. 22 | alias void delegate() nothrow @system SignalHandler; 23 | 24 | // https://github.com/D-Programming-Language/druntime/pull/759 25 | version(OSX) private 26 | { 27 | enum SIG_BLOCK = 1; 28 | enum SIG_UNBLOCK = 2; 29 | enum SIG_SETMASK = 3; 30 | } 31 | 32 | // https://github.com/D-Programming-Language/druntime/pull/1140 33 | version(FreeBSD) private 34 | { 35 | enum SIG_BLOCK = 1; 36 | enum SIG_UNBLOCK = 2; 37 | enum SIG_SETMASK = 3; 38 | } 39 | 40 | /// Register a handler for a POSIX signal. 41 | void addSignalHandler(int signum, SignalHandler fn) 42 | { 43 | handlers[signum].add(fn, { 44 | alias sigfn_t = typeof(signal(0, null)); 45 | auto old = signal(signum, cast(sigfn_t)&sighandle); 46 | assert(old == SIG_DFL || old == SIG_IGN, "A signal handler was already set"); 47 | }); 48 | } 49 | 50 | /// Unregister a previously registered signal handler. 51 | void removeSignalHandler(int signum, SignalHandler fn) 52 | { 53 | handlers[signum].remove(fn, { 54 | signal(signum, SIG_DFL); 55 | }); 56 | } 57 | 58 | // *************************************************************************** 59 | 60 | /// If the signal signum is raised during execution of code, 61 | /// ignore it. Returns true if the signal was raised. 62 | bool collectSignal(int signum, void delegate() code) 63 | { 64 | sigset_t mask; 65 | sigemptyset(&mask); 66 | sigaddset(&mask, signum); 67 | errnoEnforce(pthread_sigmask(SIG_BLOCK, &mask, null) != -1); 68 | 69 | bool result; 70 | { 71 | scope(exit) 72 | errnoEnforce(pthread_sigmask(SIG_UNBLOCK, &mask, null) != -1); 73 | 74 | scope(exit) 75 | { 76 | static if (is(typeof(&sigpending))) 77 | { 78 | errnoEnforce(sigpending(&mask) == 0); 79 | auto m = sigismember(&mask, signum); 80 | errnoEnforce(m >= 0); 81 | result = m != 0; 82 | if (result) 83 | { 84 | int s; 85 | errnoEnforce(sigwait(&mask, &s) == 0); 86 | assert(s == signum); 87 | } 88 | } 89 | else 90 | { 91 | timespec zerotime; 92 | result = sigtimedwait(&mask, null, &zerotime) == 0; 93 | } 94 | } 95 | 96 | code(); 97 | } 98 | 99 | return result; 100 | } 101 | 102 | private: 103 | 104 | enum SIGMAX = 100; 105 | 106 | synchronized class HandlerSet 107 | { 108 | alias T = SignalHandler; 109 | private T[] handlers; 110 | 111 | void add(T fn, scope void delegate() register) 112 | { 113 | if (handlers.length == 0) 114 | register(); 115 | handlers ~= cast(shared)fn; 116 | } 117 | void remove(T fn, scope void delegate() deregister) 118 | { 119 | foreach (i, lfn; handlers) 120 | if (lfn is fn) 121 | { 122 | handlers = handlers[0..i] ~ handlers[i+1..$]; 123 | if (handlers.length == 0) 124 | deregister(); 125 | return; 126 | } 127 | assert(0); 128 | } 129 | const(T)[] get() pure nothrow @nogc { return cast(const(T[]))handlers; } 130 | } 131 | 132 | shared HandlerSet[SIGMAX] handlers; 133 | 134 | shared static this() { foreach (ref h; handlers) h = new HandlerSet; } 135 | 136 | extern(C) void sighandle(int signum) nothrow @system 137 | { 138 | if (signum >= 0 && signum < handlers.length) 139 | foreach (fn; handlers[signum].get()) 140 | fn(); 141 | } 142 | -------------------------------------------------------------------------------- /sys/vfs/curl.d: -------------------------------------------------------------------------------- 1 | /** 2 | * VFS driver for curl. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.vfs.curl; 15 | 16 | private: 17 | 18 | import ae.sys.vfs; 19 | 20 | import etc.c.curl : CurlOption; 21 | import std.net.curl; 22 | import std.string; 23 | 24 | class CurlVFS : VFS 25 | { 26 | override void[] read(string path) { return get!(AutoProtocol, ubyte)(path); } 27 | override void write(string path, const(void)[] data) { put!(AutoProtocol, ubyte)(path, cast(ubyte[])data); } 28 | override bool exists(string path) 29 | { 30 | auto proto = path.split("://")[0]; 31 | if (proto == "http" || proto == "https") 32 | { 33 | auto http = HTTP(path); 34 | http.method = HTTP.Method.head; 35 | bool ok = false; 36 | http.onReceiveStatusLine = (statusLine) { ok = statusLine.code < 400; }; 37 | http.perform(); 38 | return ok; 39 | } 40 | else 41 | { 42 | try 43 | { 44 | read(path); 45 | return true; 46 | } 47 | catch (Exception e) 48 | return false; 49 | } 50 | } 51 | override void remove(string path) { del(path); } 52 | override void mkdirRecurse(string path) { assert(false, "Operation not supported"); } 53 | 54 | static this() 55 | { 56 | registry["http"] = 57 | registry["https"] = 58 | registry["ftp"] = 59 | registry["ftps"] = 60 | // std.net.curl (artificially) restricts supported protocols to the above 61 | //registry["scp"] = 62 | //registry["sftp"] = 63 | //registry["telnet"] = 64 | //registry["ldap"] = 65 | //registry["ldaps"] = 66 | //registry["dict"] = 67 | //registry["file"] = 68 | //registry["tftp"] = 69 | new CurlVFS(); 70 | } 71 | } 72 | 73 | debug(ae_unittest) unittest 74 | { 75 | if (false) 76 | { 77 | assert( "http://thecybershadow.net/robots.txt".exists); 78 | assert(!"http://thecybershadow.net/nonexistent".exists); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /sys/vfs/net.d: -------------------------------------------------------------------------------- 1 | /** 2 | * VFS driver over ae.sys.net. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.vfs.net; 15 | 16 | private: 17 | 18 | import ae.sys.vfs; 19 | 20 | import ae.sys.net; 21 | 22 | class NetVFS : VFS 23 | { 24 | override void[] read(string path) { return getFile(path); } 25 | override void copy(string src, string dst) { downloadFile(src, dst); } 26 | override bool exists(string path) { return urlOK(path); } 27 | 28 | static this() 29 | { 30 | registry["http"] = 31 | registry["https"] = 32 | new NetVFS(); 33 | } 34 | 35 | override void remove(string path) { assert(false, "NetVFS is read-only"); } 36 | override void mkdirRecurse(string path) { assert(false, "NetVFS is read-only"); } 37 | override void write(string path, const(void)[] data) { assert(false, "NetVFS is read-only"); } 38 | } 39 | 40 | debug(ae_unittest) unittest 41 | { 42 | if (false) 43 | { 44 | assert( "http://thecybershadow.net/robots.txt".exists); 45 | assert(!"http://thecybershadow.net/nonexistent".exists); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sys/vfs_curl.d: -------------------------------------------------------------------------------- 1 | /** 2 | * VFS driver for curl. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | deprecated module ae.sys.vfs_curl; 15 | 16 | deprecated import ae.sys.vfs.curl; 17 | -------------------------------------------------------------------------------- /sys/windows/dll.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Windows DLL utility code. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.dll; 15 | version (Windows): 16 | 17 | import ae.sys.windows.imports; 18 | mixin(importWin32!q{winbase}); 19 | mixin(importWin32!q{windef}); 20 | 21 | /// Loads or retrieves the handle of a DLL. 22 | /// As there will be only one template instantiation 23 | /// per unique DLL string, LoadLibrary will be called 24 | /// at most once per unique "dll" parameter. 25 | @property HMODULE moduleHandle(string dll)() 26 | { 27 | import ae.sys.windows.exception; 28 | static HMODULE hModule = null; 29 | if (!hModule) 30 | hModule = LoadLibrary(dll).wenforce("LoadLibrary"); 31 | return hModule; 32 | } 33 | 34 | /// Given a static function declaration, generate a loader with the same name in the current scope 35 | /// that loads the function dynamically from the given DLL. 36 | mixin template DynamicLoad(alias F, string DLL, string NAME=__traits(identifier, F)) 37 | { 38 | static import std.traits; 39 | 40 | static std.traits.ReturnType!F _loader(ARGS...)(ARGS args) 41 | { 42 | import ae.sys.windows.exception; 43 | import ae.sys.windows.imports; 44 | mixin(importWin32!q{winbase}); 45 | 46 | alias typeof(&F) FP; 47 | static FP fp = null; 48 | if (!fp) 49 | fp = cast(FP)wenforce(GetProcAddress(moduleHandle!DLL, NAME), "GetProcAddress"); 50 | return fp(args); 51 | } 52 | 53 | mixin(`alias _loader!(std.traits.ParameterTypeTuple!F) ` ~ NAME ~ `;`); 54 | } 55 | 56 | /// Ditto 57 | mixin template DynamicLoadMulti(string DLL, FUNCS...) 58 | { 59 | static if (FUNCS.length) 60 | { 61 | mixin DynamicLoad!(FUNCS[0], DLL); 62 | mixin DynamicLoadMulti!(DLL, FUNCS[1..$]); 63 | } 64 | } 65 | 66 | debug(ae_unittest) mixin(importWin32!q{winuser}); 67 | 68 | /// 69 | debug(ae_unittest) unittest 70 | { 71 | mixin DynamicLoad!(GetVersion, "kernel32.dll"); 72 | GetVersion(); // called via GetProcAddress 73 | 74 | // Multiple imports 75 | mixin DynamicLoadMulti!("user32.dll", GetDC, ReleaseDC); 76 | } 77 | -------------------------------------------------------------------------------- /sys/windows/exception.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Windows exceptions. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.exception; 15 | version (Windows): 16 | 17 | import core.sys.windows.windows; 18 | 19 | import std.string; 20 | 21 | import ae.sys.windows.text; 22 | 23 | /// Encapsulates a Windows API error code. 24 | /// Populates the message with an OS-provided error string. 25 | class WindowsException : Exception 26 | { 27 | DWORD code; /// The error code. 28 | 29 | this(DWORD code, string str=null) 30 | { 31 | this.code = code; 32 | 33 | wchar *lpMsgBuf = null; 34 | FormatMessageW( 35 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 36 | null, 37 | code, 38 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 39 | cast(LPWSTR)&lpMsgBuf, 40 | 0, 41 | null); 42 | 43 | auto message = lpMsgBuf.fromWString(); 44 | if (lpMsgBuf) 45 | LocalFree(lpMsgBuf); 46 | 47 | message = strip(message); 48 | message ~= format(" (error %d)", code); 49 | if (str) 50 | message = str ~ ": " ~ message; 51 | 52 | super(message); 53 | } /// 54 | } 55 | 56 | /// `enforce` variant using `GetLastError`. 57 | T wenforce(T)(T cond, string str=null) 58 | { 59 | if (cond) 60 | return cond; 61 | 62 | throw new WindowsException(GetLastError(), str); 63 | } 64 | -------------------------------------------------------------------------------- /sys/windows/imports.d: -------------------------------------------------------------------------------- 1 | /** 2 | * win32 / core.sys.windows package selection. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.imports; 15 | 16 | // Using a string mixin instead of a mixin template due to 17 | // https://issues.dlang.org/show_bug.cgi?id=15925 18 | /// String mixin for importing an appropriate package providing D 19 | /// bindings for the Windows API. 20 | template importWin32(string moduleName, string access = null, string selective = null) 21 | { 22 | // All Druntime headers are version(Windows) 23 | version (Windows) 24 | enum useDruntime = __VERSION__ >= 2070; 25 | else 26 | enum useDruntime = false; 27 | 28 | enum importWin32 = 29 | access ~ 30 | " import " ~ 31 | (useDruntime ? "core.sys.windows" : "win32") ~ 32 | "." ~ 33 | moduleName ~ 34 | " " ~ 35 | (selective ? ":" : "") ~ 36 | selective ~ 37 | ";"; 38 | } 39 | -------------------------------------------------------------------------------- /sys/windows/input.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Windows input utility code. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.input; 15 | version (Windows): 16 | 17 | import ae.sys.windows.imports; 18 | mixin(importWin32!q{winbase}); 19 | mixin(importWin32!q{windef}); 20 | mixin(importWin32!q{winuser}); 21 | 22 | /// Send WM_COPYDATA to the specified window. 23 | void sendCopyData(HWND hWnd, DWORD n, const(void)[] buf) 24 | { 25 | COPYDATASTRUCT cds; 26 | cds.dwData = n; 27 | cds.cbData = cast(uint)buf.length; 28 | cds.lpData = cast(PVOID)buf.ptr; 29 | SendMessage(hWnd, WM_COPYDATA, 0, cast(LPARAM)&cds); 30 | } 31 | 32 | private enum MAPVK_VK_TO_VSC = 0; 33 | 34 | /// Simulate keyboard input. 35 | void keyDown(ubyte c) { keybd_event(c, cast(ubyte)MapVirtualKey(c, MAPVK_VK_TO_VSC), 0 , 0); } 36 | void keyUp (ubyte c) { keybd_event(c, cast(ubyte)MapVirtualKey(c, MAPVK_VK_TO_VSC), KEYEVENTF_KEYUP, 0); } /// ditto 37 | 38 | void press(ubyte c, uint delay=0) 39 | { 40 | if (c) keyDown(c); 41 | Sleep(delay); 42 | if (c) keyUp(c); 43 | Sleep(delay); 44 | } /// ditto 45 | 46 | void keyDownOn(HWND h, ubyte c) { PostMessage(h, WM_KEYDOWN, c, MapVirtualKey(c, MAPVK_VK_TO_VSC) << 16); } /// ditto 47 | void keyUpOn (HWND h, ubyte c) { PostMessage(h, WM_KEYUP , c, MapVirtualKey(c, MAPVK_VK_TO_VSC) << 16); } /// ditto 48 | 49 | void pressOn(HWND h, ubyte c, uint delay=0) 50 | { 51 | if (c) keyDownOn(h, c); 52 | Sleep(delay); 53 | if (c) keyUpOn(h, c); 54 | Sleep(delay); 55 | } /// ditto 56 | -------------------------------------------------------------------------------- /sys/windows/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Various wrapper and utility code for the Windows API. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows; 15 | version (Windows): 16 | 17 | public import ae.sys.windows.exception; 18 | public import ae.sys.windows.dll; 19 | public import ae.sys.windows.input; 20 | public import ae.sys.windows.misc; 21 | public import ae.sys.windows.process; 22 | public import ae.sys.windows.text; 23 | public import ae.sys.windows.window; 24 | -------------------------------------------------------------------------------- /sys/windows/pe/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Support for the Windows PE executable format. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.pe; 15 | 16 | public import ae.sys.windows.pe.pe; 17 | -------------------------------------------------------------------------------- /sys/windows/text.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Miscellaneous Windows utility code. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.text; 15 | version (Windows): 16 | 17 | import core.sys.windows.windows; 18 | 19 | import std.utf; 20 | 21 | /// Convert from a potentially zero-terminated wide string. 22 | string fromWString(in wchar[] buf) 23 | { 24 | foreach (i, c; buf) 25 | if (!c) 26 | return toUTF8(buf[0..i]); 27 | return toUTF8(buf); 28 | } 29 | 30 | /// Convert from a zero-terminated wide string. 31 | string fromWString(in wchar* buf) 32 | { 33 | if (!buf) return null; 34 | const(wchar)* p = buf; 35 | for (; *p; p++) {} 36 | return toUTF8(buf[0..p-buf]); 37 | } 38 | 39 | /// Convert to a zero-terminated wide string. 40 | LPCWSTR toWStringz(string s) 41 | { 42 | return s is null ? null : toUTF16z(s); 43 | } 44 | -------------------------------------------------------------------------------- /sys/windows/window.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Windows GUI window utility code. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.sys.windows.window; 15 | version (Windows): 16 | 17 | import std.range; 18 | import std.utf; 19 | 20 | import ae.sys.windows.imports; 21 | mixin(importWin32!q{winbase}); 22 | mixin(importWin32!q{winnt}); 23 | mixin(importWin32!q{winuser}); 24 | 25 | import ae.sys.windows.exception; 26 | import ae.sys.windows.text; 27 | 28 | /// `FindWindowExW` wrapper as a D range interface. 29 | struct WindowIterator 30 | { 31 | private: 32 | LPCWSTR szClassName, szWindowName; 33 | HWND hParent, h; 34 | 35 | public: 36 | @property 37 | bool empty() const { return h is null; } /// 38 | 39 | @property 40 | HWND front() const { return cast(HWND)h; } /// 41 | 42 | void popFront() 43 | { 44 | h = FindWindowExW(hParent, h, szClassName, szWindowName); 45 | } /// 46 | } 47 | 48 | WindowIterator windowIterator(string szClassName, string szWindowName, HWND hParent=null) 49 | { 50 | auto iterator = WindowIterator(toWStringz(szClassName), toWStringz(szWindowName), hParent); 51 | iterator.popFront(); // initiate search 52 | return iterator; 53 | } /// ditto 54 | 55 | private static wchar[0xFFFF] textBuf = void; 56 | 57 | string _windowStringQuery(alias FUNC)(HWND h) 58 | { 59 | SetLastError(0); 60 | auto result = FUNC(h, textBuf.ptr, textBuf.length); 61 | if (result) 62 | return textBuf[0..result].toUTF8(); 63 | else 64 | { 65 | auto code = GetLastError(); 66 | if (code) 67 | throw new WindowsException(code, __traits(identifier, FUNC)); 68 | else 69 | return null; 70 | } 71 | } 72 | 73 | alias _windowStringQuery!GetClassNameW getClassName ; /// `GetClassNameW` wrapper. 74 | alias _windowStringQuery!GetWindowTextW getWindowText; /// `GetWIndowTextW` wrapper. 75 | 76 | /// Create an utility hidden window. 77 | HWND createHiddenWindow(string name, WNDPROC proc) 78 | { 79 | auto szName = toWStringz(name); 80 | 81 | HINSTANCE hInstance = GetModuleHandle(null); 82 | 83 | WNDCLASSEXW wcx; 84 | 85 | wcx.cbSize = wcx.sizeof; 86 | wcx.lpfnWndProc = proc; 87 | wcx.hInstance = hInstance; 88 | wcx.lpszClassName = szName; 89 | wenforce(RegisterClassExW(&wcx), "RegisterClassEx failed"); 90 | 91 | HWND hWnd = CreateWindowW( 92 | szName, // name of window class 93 | szName, // title-bar string 94 | WS_OVERLAPPEDWINDOW, // top-level window 95 | CW_USEDEFAULT, // default horizontal position 96 | CW_USEDEFAULT, // default vertical position 97 | CW_USEDEFAULT, // default width 98 | CW_USEDEFAULT, // default height 99 | null, // no owner window 100 | null, // use class menu 101 | hInstance, // handle to application instance 102 | null); // no window-creation data 103 | wenforce(hWnd, "CreateWindow failed"); 104 | 105 | return hWnd; 106 | } 107 | -------------------------------------------------------------------------------- /testflags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eEuo pipefail 3 | 4 | # Runs in CI. 5 | # Test compilation with some flag combinations: 6 | # - All version and debug names, extracted from the source code. 7 | # Guard against bit rot by ensuring things like debug code continue to compile. 8 | # - Preview compiler features. 9 | # Detect problems early. 10 | 11 | bad_flags=( 12 | # Built-in 13 | -version=AArch64 14 | -version=all 15 | -version=BigEndian 16 | -version=CRuntime_Bionic 17 | -version=CRuntime_DigitalMars 18 | -version=CRuntime_Microsoft 19 | -version=Darwin 20 | -version=DigitalMars 21 | -version=D_LP64 22 | -version=FreeBSD 23 | -version=GNU 24 | -version=iOS 25 | -version=LDC 26 | -version=linux 27 | -version=LittleEndian 28 | -version=NetBSD 29 | -version=none 30 | -version=OpenBSD 31 | -version=OSX 32 | -version=Posix 33 | -version=TVOS 34 | -version=unittest 35 | -version=WatchOS 36 | -version=Win64 37 | -version=Windows 38 | -version=X86 39 | -version=X86_64 40 | 41 | # Require special dependencies 42 | -version=LIBEV 43 | 44 | # "no longer has any effect" 45 | -preview=dip25 46 | # Currently very buggy, segfaults a lot 47 | -preview=dip1021 48 | # Seems unfinished, see https://forum.dlang.org/post/t05jts$2j48$1@digitalmars.com 49 | -preview=nosharedaccess 50 | # Includes the above 51 | -preview=all 52 | ) 53 | 54 | mapfile -t files < <(git ls-files) 55 | mapfile -t all_flags < <( 56 | { 57 | printf -- '%s\n' '-debug' 58 | dmd -preview=h | awk '/^ (=[^ ]*) .*/ {print "-preview" $1}' 59 | cat "${files[@]}" | 60 | sed -n 's/.*\b\(debug\|version\) *( *\([^()~" ]*\) *).*/-\1=\2/p' | 61 | sort -u 62 | } | grep -vFf <(printf -- '%s\n' "${bad_flags[@]}") 63 | ) 64 | 65 | find . -maxdepth 1 \( -name '*.ok' -o -name '*.out' \) -delete 66 | 67 | # Runs `check` for each tested flag combination. 68 | function check_all() { 69 | check 'no flags' 70 | 71 | for flag in "${all_flags[@]}" ; do 72 | check "$flag" "$flag" 73 | done 74 | 75 | check 'all flags' "${all_flags[@]}" 76 | } 77 | 78 | function check() { 79 | local name=$1 80 | local flags=("${@:2}") 81 | 82 | printf -- '%s > ./%q.out 2>&1 && touch ./%q.ok && echo "%s OK" >&2\n' \ 83 | "$(printf -- '%q ' \ 84 | dmd -color=on -i -o- -I.. -de "${flags[@]}" all.d)" \ 85 | "$name" "$name" "$name" 86 | } 87 | nproc=$(nproc) 88 | limit_ram=$(awk '/MemFree/ { printf "%d\n", $2/(2*1024*1024) }' /proc/meminfo) 89 | if (( nproc > limit_ram )) ; then nproc=$limit_ram ; fi 90 | if (( nproc < 1 )) ; then nproc=1 ; fi 91 | check_all | ( xargs -n 1 -d '\n' -P "$nproc" sh -c || true ) 92 | 93 | function check() { 94 | local name=$1 95 | 96 | if [[ ! -f ./"$name".ok ]] ; then 97 | printf -- '%s failed! Output:\n' "$name" 98 | cat ./"$name".out 99 | exit 1 100 | fi 101 | } 102 | check_all 103 | 104 | # Check unittest blocks 105 | if git grep '^\s*unittest' ; then 106 | printf 'All unittest blocks must have a debug(ae_unittest) guard!\n' >&2 107 | exit 1 108 | fi 109 | -------------------------------------------------------------------------------- /ui/app/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * OS-dependent entry point. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.app.main; 15 | 16 | version(Windows) 17 | import ae.ui.app.windows.main; 18 | else 19 | import ae.ui.app.posix.main; 20 | -------------------------------------------------------------------------------- /ui/app/posix/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.app.posix.main 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.app.posix.main; 15 | 16 | import std.stdio; 17 | import ae.utils.exception; 18 | import ae.ui.app.application; 19 | 20 | /// A generic entry point for applications running on POSIX. 21 | int main(string[] args) 22 | { 23 | try 24 | return runApplication(args); 25 | catch (Throwable o) 26 | { 27 | stderr.writeln(formatException(o)); 28 | return 1; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/app/windows/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.app.windows.main 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.app.windows.main; 15 | 16 | version (Windows): 17 | 18 | import core.runtime; 19 | import std.utf; 20 | 21 | import ae.sys.windows.imports; 22 | mixin(importWin32!q{windef}); 23 | mixin(importWin32!q{winuser}); 24 | 25 | import ae.ui.app.application; 26 | import ae.utils.exception; 27 | 28 | /// A generic entry point for applications running on Windows. 29 | extern (Windows) 30 | int WinMain(HINSTANCE hInstance, 31 | HINSTANCE hPrevInstance, 32 | LPSTR lpCmdLine, 33 | int nCmdShow) 34 | { 35 | int result; 36 | 37 | try 38 | { 39 | Runtime.initialize(); 40 | result = runApplication(getArgs()); 41 | Runtime.terminate(); 42 | } 43 | 44 | catch (Throwable o) // catch any uncaught exceptions 45 | { 46 | MessageBoxA(null, toUTFz!LPCSTR(formatException(o)), "Error", 47 | MB_OK | MB_ICONEXCLAMATION); 48 | result = 1; // failed 49 | } 50 | 51 | return result; 52 | } 53 | 54 | private: 55 | // Following code is adapted from D's druntime\src\rt\dmain2.d 56 | 57 | import core.stdc.wchar_; 58 | import core.stdc.stdlib; 59 | 60 | mixin(importWin32!q{winbase}); 61 | mixin(importWin32!q{shellapi}); 62 | mixin(importWin32!q{winnls}); 63 | 64 | string[] getArgs() 65 | { 66 | wchar_t* wcbuf = GetCommandLineW(); 67 | size_t wclen = wcslen(wcbuf); 68 | int wargc = 0; 69 | wchar_t** wargs = CommandLineToArgvW(wcbuf, &wargc); 70 | 71 | size_t cargl = WideCharToMultiByte(CP_UTF8, 0, wcbuf, cast(uint)wclen, null, 0, null, null); 72 | 73 | char* cargp = cast(char*) malloc(cargl); 74 | char[][] args = ((cast(char[]*) malloc(wargc * (char[]).sizeof)))[0 .. wargc]; 75 | 76 | for (size_t i = 0, p = 0; i < wargc; i++) 77 | { 78 | size_t wlen = wcslen(wargs[i]); 79 | size_t clen = WideCharToMultiByte(CP_UTF8, 0, &wargs[i][0], cast(uint)wlen, null, 0, null, null); 80 | args[i] = cargp[p .. p+clen]; 81 | p += clen; assert(p <= cargl); 82 | WideCharToMultiByte(CP_UTF8, 0, &wargs[i][0], cast(uint)wlen, &args[i][0], cast(uint)clen, null, null); 83 | } 84 | LocalFree(cast(HLOCAL)wargs); 85 | wargs = null; 86 | wargc = 0; 87 | 88 | return cast(string[])args; 89 | } 90 | -------------------------------------------------------------------------------- /ui/audio/audio.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.audio 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.audio; 15 | 16 | import ae.ui.app.application; 17 | import ae.ui.audio.mixer.base; 18 | 19 | /// Abstract audio player interface. 20 | class Audio 21 | { 22 | Mixer mixer; /// 23 | 24 | /// Start driver (Application dictates settings). 25 | abstract void start(Application application); 26 | 27 | /// Stop driver (may block). 28 | abstract void stop(); 29 | } 30 | -------------------------------------------------------------------------------- /ui/audio/mixer/base.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.mixer.base 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.mixer.base; 15 | 16 | import ae.ui.audio.source.base; 17 | 18 | /// Abstract mixer interface. 19 | class Mixer 20 | { 21 | /// Add a sound to the mixer. 22 | abstract void playSound(SoundSource sound); 23 | 24 | /// Mix sounds and fill the given buffer. 25 | abstract void fillBuffer(SoundSample[] buffer) nothrow; 26 | } 27 | -------------------------------------------------------------------------------- /ui/audio/mixer/software.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.mixer.software 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.mixer.software; 15 | 16 | import std.algorithm.mutation; 17 | 18 | import ae.ui.audio.mixer.base; 19 | import ae.ui.audio.source.base; 20 | 21 | /// Software mixer implementation. 22 | class SoftwareMixer : Mixer 23 | { 24 | struct Stream 25 | { 26 | SoundSource source; /// 27 | size_t pos; /// 28 | } /// Currently playing streams. 29 | Stream[] streams; /// ditto 30 | 31 | override void playSound(SoundSource sound) 32 | { 33 | // Note: sounds will begin to play on the next sound frame 34 | // (fillBuffer invocation), so any two sounds' start time will 35 | // always be a multiple of the buffer length apart. 36 | streams ~= Stream(sound, 0); 37 | 38 | // TODO: check sample rate 39 | } /// 40 | 41 | // Temporary storage of procedural streams 42 | private SoundSample[] streamBuffer; 43 | 44 | // TODO: multiple channels 45 | override void fillBuffer(SoundSample[] buffer) nothrow 46 | { 47 | buffer[] = 0; 48 | 49 | foreach_reverse (i, ref stream; streams) 50 | { 51 | foreach (channel; 0..1) 52 | { 53 | const(SoundSample)[] samples; 54 | if (stream.source.procedural) 55 | { 56 | if (streamBuffer.length < buffer.length) 57 | streamBuffer.length = buffer.length; 58 | auto copiedSamples = stream.source.copySamples(channel, stream.pos, streamBuffer); 59 | samples = streamBuffer[0..copiedSamples]; 60 | } 61 | else 62 | samples = stream.source.getSamples(channel, stream.pos, buffer.length); 63 | 64 | buffer[0..samples.length] += samples[]; // Fast vector math! 65 | 66 | if (samples.length < buffer.length) // EOF? 67 | streams = streams.remove(i); 68 | } 69 | stream.pos += buffer.length; 70 | } 71 | } /// 72 | } 73 | -------------------------------------------------------------------------------- /ui/audio/sdl2/audio.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.sdl.audio 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.sdl2.audio; 15 | 16 | import derelict.sdl2.sdl; 17 | 18 | import ae.ui.app.application; 19 | import ae.ui.audio.audio; 20 | import ae.ui.audio.source.base; 21 | import ae.ui.shell.sdl2.shell; 22 | 23 | /// SDL2 implementation of `Audio`. 24 | class SDL2Audio : Audio 25 | { 26 | override void start(Application application) 27 | { 28 | assert(mixer, "No mixer set"); 29 | 30 | SDL_AudioSpec spec; 31 | // TODO: make this customizable 32 | spec.freq = 44100; 33 | spec.format = AUDIO_S16; 34 | spec.channels = 1; 35 | spec.samples = 1024; 36 | spec.callback = &callback; 37 | spec.userdata = cast(void*)this; 38 | 39 | sdlEnforce(SDL_OpenAudio(&spec, null) >= 0, "SDL_OpenAudio"); 40 | 41 | SDL_PauseAudio(0); 42 | } /// 43 | 44 | override void stop() 45 | { 46 | SDL_CloseAudio(); 47 | } /// 48 | 49 | private static extern(C) void callback(void *userData, ubyte *bufferPtr, int length) nothrow 50 | { 51 | auto buffer = cast(SoundSample[])bufferPtr[0..length]; 52 | SDL2Audio instance = cast(SDL2Audio)userData; 53 | instance.mixer.fillBuffer(buffer); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ui/audio/source/base.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.source.base 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.source.base; 15 | 16 | /// Base class of a sound source. 17 | class AbstractSoundSource(Sample) 18 | { 19 | /// Returns number of samples per second. 20 | abstract uint getSampleRate() const nothrow; 21 | 22 | /// Returns number of channels per sample. 23 | abstract size_t getNumChannels() const nothrow; 24 | 25 | /// If true, `getSamples` is not available - samples can only be read with `copySamples`. 26 | abstract bool procedural() const nothrow; 27 | 28 | /// Fill `buffer` with samples from `channel` starting with the position `start`. 29 | abstract size_t copySamples(size_t channel, size_t start, Sample[] buffer) const nothrow; 30 | 31 | /// Retrieve a slice of an internal buffer containing the samples. 32 | /// Only available if `procedural` is `false`. 33 | abstract const(Sample)[] getSamples(size_t channel, size_t start, size_t maxLength) const nothrow; 34 | } 35 | 36 | /// The sample type we'll use by default for mixing. 37 | alias SoundSample = short; 38 | 39 | /// 16-bit PCM 40 | alias SoundSource = AbstractSoundSource!SoundSample; 41 | 42 | -------------------------------------------------------------------------------- /ui/audio/source/memory.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.memory.base 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.source.memory; 15 | 16 | import std.algorithm.comparison; 17 | import std.algorithm.mutation; 18 | import std.range; 19 | import std.range.primitives; 20 | 21 | import ae.ui.audio.source.base; 22 | 23 | /// Implementation of `AbstractSoundSource` backed by a simple array of samples. 24 | template MemorySoundSource(Sample) 25 | { 26 | final class MemorySoundSource : AbstractSoundSource!Sample 27 | { 28 | Sample[] samples; /// 29 | uint sampleRate; /// 30 | 31 | this(Sample[] samples, uint sampleRate) 32 | { 33 | this.samples = samples; 34 | this.sampleRate = sampleRate; 35 | } /// 36 | 37 | override uint getSampleRate() const nothrow 38 | { 39 | return sampleRate; 40 | } /// 41 | 42 | override size_t getNumChannels() const nothrow 43 | { 44 | // TODO 45 | return 1; 46 | } /// 47 | 48 | override bool procedural() const nothrow 49 | { 50 | return false; 51 | } /// 52 | 53 | override size_t copySamples(size_t channel, size_t start, Sample[] buffer) const nothrow 54 | { 55 | auto slice = getSamples(channel, start, buffer.length); 56 | buffer[0 .. slice.length] = slice; 57 | return slice.length; 58 | } /// 59 | 60 | override const(Sample)[] getSamples(size_t channel, size_t start, size_t maxLength) const nothrow 61 | { 62 | start = min(start, samples.length); 63 | auto end = min(start + maxLength, samples.length); 64 | return samples[start .. end]; 65 | } /// 66 | } 67 | } 68 | 69 | /// Construct a `MemorySoundSource` from an array. 70 | MemorySoundSource!Sample memorySoundSource(Sample)(Sample[] samples, uint sampleRate) 71 | { 72 | return new MemorySoundSource!Sample(samples, sampleRate); 73 | } 74 | 75 | debug(ae_unittest) unittest 76 | { 77 | auto w = memorySoundSource([short.max, short.min], 44100); 78 | } 79 | -------------------------------------------------------------------------------- /ui/audio/source/wave.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.audio.wave.base 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.audio.source.wave; 15 | 16 | import std.algorithm.mutation; 17 | import std.range; 18 | import std.range.primitives; 19 | 20 | import ae.ui.audio.source.base; 21 | 22 | /// Implementation of `AbstractSoundSource` backed by a D range of samples. 23 | template WaveSoundSource(Wave) 24 | { 25 | alias Sample = typeof(Wave.init.front); 26 | 27 | class WaveSoundSource : AbstractSoundSource!Sample 28 | { 29 | Wave wave; /// 30 | uint sampleRate; /// 31 | 32 | this(Wave wave, uint sampleRate) 33 | { 34 | this.wave = wave; 35 | this.sampleRate = sampleRate; 36 | } /// 37 | 38 | override uint getSampleRate() const nothrow 39 | { 40 | return sampleRate; 41 | } /// 42 | 43 | override size_t getNumChannels() const nothrow 44 | { 45 | // TODO 46 | return 1; 47 | } /// 48 | 49 | override bool procedural() const nothrow 50 | { 51 | return true; 52 | } /// 53 | 54 | override size_t copySamples(size_t channel, size_t start, Sample[] buffer) const nothrow 55 | { 56 | auto w = cast(Wave)wave; // Break constness because Map.save is not const 57 | auto remaining = copy(w.drop(start).take(buffer.length), buffer); 58 | return buffer.length - remaining.length; 59 | } /// 60 | 61 | override const(Sample)[] getSamples(size_t channel, size_t start, size_t maxLength) const nothrow 62 | { 63 | assert(false, "Procedural"); 64 | } /// 65 | } 66 | } 67 | 68 | /// Construct a `WaveSoundSource` from a range of samples. 69 | WaveSoundSource!Wave waveSoundSource(Wave)(Wave wave, uint sampleRate) 70 | { 71 | return new WaveSoundSource!Wave(wave, sampleRate); 72 | } 73 | 74 | debug(ae_unittest) unittest 75 | { 76 | auto w = waveSoundSource([short.max, short.min], 44100); 77 | } 78 | -------------------------------------------------------------------------------- /ui/shell/events.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.shell.events 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.shell.events; 15 | 16 | /// Mouse button indices. 17 | enum MouseButton : ubyte 18 | { 19 | Left, /// 20 | Right, /// 21 | Middle, /// 22 | WheelUp, /// 23 | WheelDown, /// 24 | } 25 | 26 | /// Mouse button mask. 27 | enum MouseButtons : ubyte 28 | { 29 | None = 0, /// 30 | Left = 1<<0, /// 31 | Right = 1<<1, /// 32 | Middle = 1<<2, /// 33 | WheelUp = 1<<3, /// 34 | WheelDown = 1<<4, /// 35 | } 36 | 37 | /// A few key definitions. 38 | enum Key 39 | { 40 | unknown , /// 41 | esc , /// 42 | up , /// 43 | down , /// 44 | left , /// 45 | right , /// 46 | pageUp , /// 47 | pageDown , /// 48 | home , /// 49 | end , /// 50 | space , /// 51 | } 52 | 53 | /// Joystick/D-pad directions. 54 | enum JoystickHatState 55 | { 56 | up = 1, /// 57 | right = 2, /// 58 | down = 4, /// 59 | left = 8, /// 60 | } 61 | -------------------------------------------------------------------------------- /ui/shell/shell.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.shell.shell 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.shell.shell; 15 | 16 | import ae.ui.video.video; 17 | import ae.ui.audio.audio; 18 | 19 | /// A "shell" handles OS window management, input handling, and various other platform-dependent tasks. 20 | class Shell 21 | { 22 | /// Run the main loop. 23 | abstract void run(); 24 | 25 | /// Set window title. 26 | abstract void setCaption(string caption); 27 | 28 | /// Request the event loop to stop. 29 | /// May be called from another thread. 30 | void quit() 31 | { 32 | if (!quitting) 33 | { 34 | quitting = true; 35 | prod(); 36 | } 37 | } 38 | 39 | /// Wake event thread with a no-op event. 40 | abstract void prod(); 41 | 42 | Video video; /// `Video` implementation. 43 | Audio audio; /// `Audio` implementation. 44 | 45 | protected: 46 | bool quitting; 47 | } 48 | 49 | /// Specifies the window / screen mode. 50 | enum ScreenMode 51 | { 52 | windowed , /// 53 | maximized , /// 54 | fullscreen , /// 55 | windowedFullscreen, /// 56 | } 57 | 58 | /// The default / remembered screen settings. 59 | struct ShellSettings 60 | { 61 | uint fullScreenX = 1024; /// Full-screen resolution. 62 | uint fullScreenY = 768; /// ditto 63 | uint windowSizeX = 800; /// Window size. 64 | uint windowSizeY = 600; /// ditto 65 | int windowPosX = int.min; /// Windows position. `int.min` means unset. 66 | int windowPosY = int.min; /// ditto 67 | ScreenMode screenMode = ScreenMode.windowed; /// Window / screen mode. 68 | } 69 | -------------------------------------------------------------------------------- /ui/timer/sdl2/timer.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.timer.sdl2.timer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.timer.sdl2.timer; 15 | 16 | import derelict.sdl2.sdl; 17 | 18 | public import ae.ui.timer.timer; 19 | import ae.ui.app.application; 20 | import ae.ui.shell.sdl2.shell : sdlEnforce; 21 | 22 | /// SDL implementation of `Timer`. 23 | final class SDLTimer : Timer 24 | { 25 | override TimerEvent setTimeout (AppCallback fn, uint ms) { return add(fn, ms, false); } /// 26 | override TimerEvent setInterval(AppCallback fn, uint ms) { return add(fn, ms, true ); } /// 27 | 28 | private: 29 | TimerEvent add(AppCallback fn, uint ms, bool recurring) 30 | { 31 | auto event = new SDLTimerEvent; 32 | event.fn = fn; 33 | event.recurring = recurring; 34 | event.id = sdlEnforce(SDL_AddTimer(ms, &sdlCallback, cast(void*)event)); 35 | return event; 36 | } 37 | 38 | extern(C) static uint sdlCallback(uint ms, void* param) nothrow 39 | { 40 | auto event = cast(SDLTimerEvent)param; 41 | try 42 | if (event.call()) 43 | return ms; 44 | else 45 | return 0; 46 | catch (Exception e) 47 | throw new Error("Exception thrown from timer event", e); 48 | } 49 | } 50 | 51 | private final class SDLTimerEvent : TimerEvent 52 | { 53 | SDL_TimerID id; 54 | AppCallback fn; 55 | bool recurring, calling, cancelled; 56 | 57 | // Returns true if it should be rescheduled. 58 | bool call() 59 | { 60 | calling = true; 61 | fn.call(); 62 | calling = false; 63 | return recurring && !cancelled; 64 | } 65 | 66 | override void cancel() 67 | { 68 | if (calling) 69 | cancelled = true; 70 | else 71 | SDL_RemoveTimer(id); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ui/timer/thread/timer.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.timer.thread.timer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.timer.thread.timer; 15 | 16 | import core.thread; 17 | import core.sync.semaphore; 18 | 19 | public import ae.ui.timer.timer; 20 | import ae.ui.app.application; 21 | import ae.sys.timing; 22 | 23 | private alias ae.sys.timing.Timer SysTimer; 24 | private alias ae.ui.timer.timer.Timer Timer; 25 | 26 | /// A simple thread-based `Timer` implementation. 27 | final class ThreadTimer : Timer 28 | { 29 | this() 30 | { 31 | sysTimer = new SysTimer; 32 | semaphore = new Semaphore; 33 | auto thread = new Thread(&threadProc); 34 | thread.isDaemon = true; 35 | thread.start(); 36 | } /// 37 | 38 | protected: 39 | SysTimer sysTimer; 40 | Semaphore semaphore; 41 | shared bool prodding; 42 | 43 | override TimerEvent setTimeout (AppCallback fn, uint ms) { return new ThreadTimerEvent(fn, ms, false); } 44 | override TimerEvent setInterval(AppCallback fn, uint ms) { return new ThreadTimerEvent(fn, ms, true ); } 45 | 46 | private: 47 | void threadProc() 48 | { 49 | while (true) 50 | { 51 | Duration remainingTime; 52 | 53 | synchronized(sysTimer) 54 | { 55 | auto now = MonoTime.currTime(); 56 | prodding = true; 57 | sysTimer.prod(now); 58 | prodding = false; 59 | 60 | now = MonoTime.currTime(); 61 | remainingTime = sysTimer.getRemainingTime(now); 62 | } 63 | 64 | if (remainingTime == Duration.max) 65 | semaphore.wait(); 66 | else 67 | semaphore.wait(remainingTime); 68 | } 69 | } 70 | 71 | final class ThreadTimerEvent : TimerEvent 72 | { 73 | AppCallback fn; 74 | bool recurring; 75 | TimerTask task; 76 | uint ms; 77 | 78 | this(AppCallback fn, uint ms, bool recurring) 79 | { 80 | auto now = MonoTime.currTime(); 81 | this.fn = fn; 82 | this.ms = ms; 83 | this.recurring = recurring; 84 | this.task = new TimerTask(&taskCallback); 85 | synchronized(sysTimer) 86 | sysTimer.add(task, now + ms.msecs); 87 | } 88 | 89 | void taskCallback(SysTimer timer, TimerTask task) 90 | { 91 | if (recurring) 92 | { 93 | auto now = MonoTime.currTime(); 94 | timer.add(task, now + ms.msecs); 95 | } 96 | fn.call(); 97 | } 98 | 99 | override void cancel() 100 | { 101 | if (prodding) // cancel called from timer event handler, synchronization would cause a deadlock 102 | task.cancel(); 103 | else 104 | synchronized(sysTimer) 105 | task.cancel(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ui/timer/timer.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.timer.timer 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.timer.timer; 15 | 16 | import ae.ui.app.application; 17 | 18 | /// Abstract timer interface. 19 | class Timer 20 | { 21 | /// Run `fn` after `ms` milliseconds. 22 | abstract TimerEvent setTimeout (AppCallback fn, uint ms); 23 | /// Run `fn` every `ms` milliseconds. 24 | abstract TimerEvent setInterval(AppCallback fn, uint ms); 25 | } 26 | 27 | /// Abstract interface for registered timer events. 28 | class TimerEvent 29 | { 30 | /// Cancel the timer task. 31 | abstract void cancel(); 32 | } 33 | -------------------------------------------------------------------------------- /ui/video/bmfont.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Rendering for simple bitmap fonts. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.video.bmfont; 15 | 16 | import ae.ui.video.renderer; 17 | import ae.utils.array; 18 | import ae.utils.graphics.fonts.draw; 19 | 20 | /// Adapter from a font (as in `ae.utils.graphics.fonts`) 21 | /// and `ProceduralTextureSource` . 22 | final class FontTextureSource(Font) : ProceduralTextureSource 23 | { 24 | this(Font font, Renderer.COLOR color) 25 | { 26 | this.font = font; 27 | this.color = color; 28 | } /// 29 | 30 | /// Draw a string. 31 | void drawText(S)(Renderer r, int x, int y, S s) 32 | { 33 | foreach (c; s) 34 | { 35 | if (font.hasGlyph(c)) 36 | { 37 | auto g = font.getGlyph(c); 38 | auto v = c * font.height; 39 | r.draw(x, y, this, 0, v, g.width, v + font.height); 40 | x += g.width; 41 | } 42 | } 43 | } 44 | 45 | protected: 46 | Font font; 47 | Renderer.COLOR color; 48 | 49 | override void getSize(out int width, out int height) 50 | { 51 | width = font.maxWidth; 52 | height = font.maxGlyph * font.height; 53 | } 54 | 55 | override void drawTo(TextureCanvas dest) 56 | { 57 | foreach (g; 0..font.maxGlyph) 58 | { 59 | dchar c = g; 60 | dest.drawText(0, g * font.height, c.asSlice, font, color); 61 | } 62 | } 63 | } 64 | 65 | debug(ae_unittest) unittest 66 | { 67 | // Test instantiation 68 | if (false) 69 | { 70 | Renderer r; 71 | import ae.utils.graphics.fonts.font8x8; 72 | FontTextureSource!Font8x8 f; 73 | f.drawText(r, 0, 0, "foo"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ui/video/sdl2/video.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.video.sdl.video 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.video.sdl2.video; 15 | 16 | import derelict.sdl2.sdl; 17 | 18 | import ae.ui.shell.sdl2.shell; 19 | import ae.ui.video.sdl2common.video; 20 | import ae.ui.video.renderer; 21 | import ae.ui.video.sdl2.renderer; 22 | 23 | /// `Video` implementation backed by `SDL2SoftwareRenderer`. 24 | class SDL2SoftwareVideo : SDL2CommonVideo 25 | { 26 | protected: 27 | override Renderer getRenderer() 28 | { 29 | return new SDL2SoftwareRenderer(renderer, screenWidth, screenHeight); 30 | } 31 | } 32 | 33 | /// `Video` implementation backed by `SDL2Renderer`. 34 | class SDL2Video : SDL2CommonVideo 35 | { 36 | protected: 37 | override Renderer getRenderer() 38 | { 39 | return new SDL2Renderer(renderer, screenWidth, screenHeight); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ui/video/software/common.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.video.software.common 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.video.software.common; 15 | 16 | 17 | /// Mixin implementing Renderer methods using Canvas. 18 | /// Mixin context: "bitmap" must return a Canvas-like object. 19 | mixin template SoftwareRenderer() 20 | { 21 | import gd = ae.utils.graphics.draw; 22 | 23 | override void putPixel(int x, int y, COLOR color) 24 | { 25 | gd.safePut(bitmap, x, y, color); 26 | } /// 27 | 28 | override void putPixels(Pixel[] pixels) 29 | { 30 | foreach (ref pixel; pixels) 31 | gd.safePut(bitmap, pixel.x, pixel.y, pixel.color); 32 | } /// 33 | 34 | override void line(float x0, float y0, float x1, float y1, COLOR color) 35 | { 36 | gd.aaLine(bitmap, x0, y0, x1, y1, color); 37 | } /// 38 | 39 | override void vline(int x, int y0, int y1, COLOR color) 40 | { 41 | gd.vline(bitmap, x, y0, y1, color); 42 | } /// 43 | 44 | override void hline(int x0, int x1, int y, COLOR color) 45 | { 46 | gd.hline(bitmap, x0, x1, y, color); 47 | } /// 48 | 49 | override void fillRect(int x0, int y0, int x1, int y1, COLOR color) 50 | { 51 | gd.fillRect(bitmap, x0, y0, x1, y1, color); 52 | } /// 53 | 54 | override void fillRect(float x0, float y0, float x1, float y1, COLOR color) 55 | { 56 | gd.aaFillRect(bitmap, x0, y0, x1, y1, color); 57 | } /// 58 | 59 | override void clear() 60 | { 61 | gd.clear(bitmap, COLOR.init); 62 | } /// 63 | 64 | override void draw(int x, int y, TextureSource source, int u0, int v0, int u1, int v1) 65 | { 66 | auto w = bitmap.crop(x, y, x+(u1-u0), y+(v1-v0)); 67 | // TODO: use drawTo when drawing entire image 68 | // source.drawTo(w.toRef()); 69 | source.getPixels.crop(u0, v0, u1, v1).blitTo(w); 70 | } /// 71 | 72 | override void draw(float x0, float y0, float x1, float y1, TextureSource source, int u0, int v0, int u1, int v1) 73 | { 74 | // assert(0, "TODO"); 75 | } /// 76 | } 77 | 78 | debug(ae_unittest) unittest 79 | { 80 | import ae.utils.graphics.color; 81 | import ae.utils.graphics.image; 82 | 83 | import ae.ui.video.renderer; 84 | 85 | class C : Renderer 86 | { 87 | Image!COLOR bitmap; 88 | 89 | mixin SoftwareRenderer; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ui/video/video.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.video.video 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.video.video; 15 | 16 | import ae.ui.app.application; 17 | 18 | /// Abstract video driver interface. 19 | class Video 20 | { 21 | public: 22 | /// Start driver (Application dictates settings). 23 | abstract void start(Application application); 24 | 25 | /// Stop driver (may block). 26 | abstract void stop(); 27 | 28 | /// Stop driver (asynchronous). 29 | abstract void stopAsync(AppCallback callback); 30 | 31 | /// Shutdown (de-initialize) video driver. Blocks. 32 | abstract void shutdown(); 33 | 34 | /// Query the window size that's currently running. 35 | /// This is different from the information in ShellSettings 36 | /// in that it returns the current client area regardless 37 | /// of the screen mode. 38 | abstract void getScreenSize(out uint width, out uint height); 39 | 40 | /// Shell hooks. 41 | AppCallback errorCallback; 42 | } 43 | -------------------------------------------------------------------------------- /ui/wm/application.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.ui.wm.application 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.ui.wm.application; 15 | 16 | import ae.ui.app.application; 17 | import ae.ui.shell.shell; 18 | import ae.ui.shell.events; 19 | import ae.ui.wm.controls.control; 20 | import ae.ui.video.renderer; 21 | 22 | /// Specialization of Application class which automatically handles framework messages. 23 | class WMApplication : Application 24 | { 25 | Shell shell; /// `Shell` implementation. 26 | RootControl root; /// The root control. 27 | 28 | this() 29 | { 30 | root = new RootControl(); 31 | } /// 32 | 33 | // ****************************** Event handlers ******************************* 34 | 35 | override void handleMouseDown(uint x, uint y, MouseButton button) 36 | { 37 | root.handleMouseDown(x, y, button); 38 | } /// 39 | 40 | override void handleMouseUp(uint x, uint y, MouseButton button) 41 | { 42 | root.handleMouseUp(x, y, button); 43 | } /// 44 | 45 | override void handleMouseMove(uint x, uint y, MouseButtons buttons) 46 | { 47 | root.handleMouseMove(x, y, buttons); 48 | } /// 49 | 50 | override void handleQuit() 51 | { 52 | shell.quit(); 53 | } /// 54 | 55 | override void handleInit() 56 | { 57 | uint w, h; 58 | shell.video.getScreenSize(w, h); 59 | root.w = w; root.h = h; 60 | root.sizeChanged(); 61 | } /// 62 | 63 | // ********************************* Rendering ********************************* 64 | 65 | override void render(Renderer s) 66 | { 67 | root.render(s, 0, 0); 68 | } /// 69 | } 70 | -------------------------------------------------------------------------------- /utils/aa_test.d: -------------------------------------------------------------------------------- 1 | module ae.utils.aa_test; 2 | 3 | package(ae): 4 | 5 | import ae.utils.array; 6 | 7 | debug(ae_unittest) unittest 8 | { 9 | int[int] aa; 10 | aa.update(1, 11 | () => 2, 12 | (ref int val) { return val; } 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /utils/async.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Asynchronous programming helpers 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | deprecated module ae.utils.async; 15 | 16 | deprecated: 17 | void asyncApply(T)(T[] arr, void delegate(T value, void delegate() next) f, void delegate() done = null) 18 | { 19 | size_t index = 0; 20 | 21 | void next() 22 | { 23 | if (index < arr.length) 24 | f(arr[index++], &next); 25 | else 26 | if (done) 27 | done(); 28 | } 29 | 30 | next(); 31 | } 32 | -------------------------------------------------------------------------------- /utils/bench.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple benchmarking/profiling code 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | deprecated module ae.utils.bench; 15 | 16 | // https://issues.dlang.org/show_bug.cgi?id=23965 17 | // deprecated: 18 | 19 | import std.datetime; 20 | import ae.sys.timing; 21 | 22 | MonoTime lastTime; 23 | 24 | static this() 25 | { 26 | lastTime = MonoTime.currTime(); 27 | } 28 | 29 | Duration elapsedTime() 30 | { 31 | auto c = MonoTime.currTime(); 32 | auto d = c - lastTime; 33 | lastTime = c; 34 | return d; 35 | } 36 | 37 | struct TimedAction 38 | { 39 | string name; 40 | Duration duration; 41 | } 42 | 43 | TimedAction[] timedActions; 44 | size_t[string] timeNameIndices; 45 | string currentAction = null; 46 | 47 | void timeEnd(string action = null) 48 | { 49 | if (action && currentAction && action != currentAction) 50 | action = currentAction ~ " / " ~ action; 51 | if (action is null) 52 | action = currentAction; 53 | if (action is null) 54 | action = "other"; 55 | currentAction = null; 56 | 57 | // ordered 58 | if (action !in timeNameIndices) 59 | { 60 | timeNameIndices[action] = timedActions.length; 61 | timedActions ~= TimedAction(action, elapsedTime()); 62 | } 63 | else 64 | timedActions[timeNameIndices[action]].duration += elapsedTime(); 65 | } 66 | 67 | 68 | void timeStart(string action = null) 69 | { 70 | timeEnd(); 71 | currentAction = action; 72 | } 73 | 74 | void timeAction(string action, void delegate() p) 75 | { 76 | timeStart(action); 77 | p(); 78 | timeEnd(action); 79 | } 80 | 81 | void clearTimes() 82 | { 83 | timedActions = null; 84 | timeNameIndices = null; 85 | lastTime = MonoTime.currTime(); 86 | } 87 | 88 | /// Retrieves current times and clears them. 89 | string getTimes()() 90 | { 91 | timeEnd(); 92 | 93 | import std.string, std.array; 94 | string[] lines; 95 | int maxLength; 96 | foreach (action; timedActions) 97 | if (!action.duration.empty) 98 | if (maxLength < action.name.length) 99 | maxLength = action.name.length; 100 | string fmt = format("%%%ds : %%10d (%%s)", maxLength); 101 | foreach (action; timedActions) 102 | if (!action.duration.empty) 103 | lines ~= format(fmt, action.name, action.duration.total!"hnsecs", action.duration); 104 | clearTimes(); 105 | return join(lines, "\n"); 106 | } 107 | 108 | void dumpTimes()() 109 | { 110 | import std.stdio; 111 | import ae.sys.console; 112 | auto times = getTimes(); 113 | if (times.length) 114 | writeln(times); 115 | } 116 | 117 | private string[] timeStack; 118 | 119 | void timePush(string action = null) 120 | { 121 | timeStack ~= currentAction; 122 | timeStart(action); 123 | } 124 | 125 | void timePop(string action = null) 126 | { 127 | timeEnd(action); 128 | timeStart(timeStack[$-1]); 129 | timeStack = timeStack[0..$-1]; 130 | } 131 | -------------------------------------------------------------------------------- /utils/container/list.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Linked list containers 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.container.list; 15 | 16 | public import ae.utils.container.listnode; 17 | 18 | import ae.utils.alloc; 19 | 20 | /// Organizes a bunch of objects in a linked list. 21 | /// Not very efficient for reference types, since it results in two allocations per object. 22 | struct ListParts(T, bool HASPREV, bool HASTAIL, alias ALLOCATOR=heapAllocator) 23 | { 24 | alias ListNode!(T, HASPREV) Node; 25 | enum ITEM_EXPR = q{.value}; 26 | 27 | struct Data 28 | { 29 | mixin ListCommon.Data!(Node*, HASPREV, HASTAIL); 30 | } 31 | 32 | static template Impl(alias data) 33 | { 34 | mixin ListCommon.Impl!(Node*, HASPREV, HASTAIL, data) common; 35 | 36 | Node* pushFront(T v) 37 | { 38 | auto node = ALLOCATOR.allocate!Node(); 39 | node.value = v; 40 | common.pushFront(node); 41 | return node; 42 | } 43 | 44 | static if (HASTAIL) 45 | Node* pushBack(T v) 46 | { 47 | auto node = ALLOCATOR.allocate!Node(); 48 | node.value = v; 49 | common.pushBack(node); 50 | return node; 51 | } 52 | 53 | static if (HASTAIL) 54 | deprecated alias pushBack add; 55 | 56 | static if (HASPREV) 57 | void remove(Node* node) 58 | { 59 | common.remove(node); 60 | static if (is(typeof(&ALLOCATOR.free))) 61 | ALLOCATOR.free(node); 62 | } 63 | } 64 | } 65 | 66 | alias PartsWrapper!ListParts List; 67 | 68 | /// Singly-ended singly-linked list. Usable as a stack. 69 | template SList(T) 70 | { 71 | alias List!(T, false, false) SList; 72 | } 73 | 74 | /// Double-ended singly-linked list. Usable as a stack or queue. 75 | template DESList(T) 76 | { 77 | alias List!(T, false, true) DESList; 78 | } 79 | 80 | /// Doubly-linked list. Usable as a stack, queue or deque. 81 | template DList(T) 82 | { 83 | alias List!(T, true, true) DList; 84 | } 85 | 86 | /// Doubly-linked but single-ended list. 87 | /// Can't be used as a queue or deque, but supports arbitrary removal. 88 | template SEDList(T) 89 | { 90 | alias List!(T, true, false) SEDList; 91 | } 92 | 93 | debug(ae_unittest) unittest 94 | { 95 | DList!int l; 96 | auto i1 = l.pushBack(1); 97 | auto i2 = l.pushBack(2); 98 | auto i3 = l.pushBack(3); 99 | l.remove(i2); 100 | 101 | int[] a; 102 | foreach (i; l) 103 | a ~= i; 104 | assert(a == [1, 3]); 105 | 106 | import std.algorithm; 107 | assert(equal(l.iterator, [1, 3])); 108 | } 109 | 110 | -------------------------------------------------------------------------------- /utils/container/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.utils.container 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.container; 15 | 16 | public import ae.utils.container.set; 17 | public import ae.utils.container.list; 18 | public import ae.utils.container.hashtable; 19 | -------------------------------------------------------------------------------- /utils/container/set.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.utils.container.set 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.container.set; 15 | 16 | /// Unordered array with O(1) insertion and removal 17 | struct Set(T, uint INITSIZE=64) 18 | { 19 | T[] data; 20 | size_t size; 21 | 22 | void opOpAssign(string OP)(T item) 23 | if (OP=="~") 24 | { 25 | if (data.length == size) 26 | data.length = size ? size * 2 : INITSIZE; 27 | data[size++] = item; 28 | } 29 | 30 | void remove(size_t index) 31 | { 32 | assert(index < size); 33 | data[index] = data[--size]; 34 | } 35 | 36 | @property T[] items() 37 | { 38 | return data[0..size]; 39 | } 40 | } 41 | 42 | debug(ae_unittest) unittest 43 | { 44 | Set!int s; 45 | s ~= 1; 46 | s ~= 2; 47 | s ~= 3; 48 | assert(s.items == [1, 2, 3]); 49 | s.remove(1); 50 | assert(s.items == [1, 3]); 51 | } 52 | -------------------------------------------------------------------------------- /utils/feed.d: -------------------------------------------------------------------------------- 1 | /** 2 | * RSS/ATOM feed generation 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.feed; 15 | 16 | import std.datetime; 17 | 18 | import ae.utils.xmlwriter; 19 | import ae.utils.time; 20 | 21 | /// ATOM writer 22 | struct AtomFeedWriter 23 | { 24 | XmlWriter xml; /// Target XML writer. 25 | 26 | private void putTag(string name)(string content) 27 | { 28 | xml.startTag!name(); 29 | xml.text(content); 30 | xml.endTag!name(); 31 | } 32 | 33 | private void putTimeTag(string name)(SysTime time) 34 | { 35 | xml.startTag!name(); 36 | .putTime!(TimeFormats.ATOM)(xml.output, time); 37 | xml.endTag!name(); 38 | } 39 | 40 | /// Start writing. 41 | void startFeed(string feedUrl, string title, SysTime updated) 42 | { 43 | xml.startDocument(); 44 | 45 | xml.startTagWithAttributes!"feed"(); 46 | xml.addAttribute!"xmlns"("http://www.w3.org/2005/Atom"); 47 | xml.endAttributes(); 48 | 49 | xml.startTagWithAttributes!"link"(); 50 | xml.addAttribute!"rel"("self"); 51 | xml.addAttribute!"type"("application/atom+xml"); 52 | xml.addAttribute!"href"(feedUrl); 53 | xml.endAttributesAndTag(); 54 | 55 | putTag!"id"(feedUrl); 56 | putTag!"title"(title); 57 | putTimeTag!"updated"(updated); 58 | } 59 | 60 | /// Add an entry. 61 | void putEntry(string url, string title, string authorName, SysTime time, string contentHtml, string link=null) 62 | { 63 | xml.startTag!"entry"(); 64 | 65 | putTag!"id"(url); 66 | putTag!"title"(title); 67 | putTimeTag!"published"(time); 68 | putTimeTag!"updated"(time); 69 | 70 | xml.startTag!"author"(); 71 | putTag!"name"(authorName); 72 | xml.endTag!"author"(); 73 | 74 | if (link) 75 | { 76 | xml.startTagWithAttributes!"link"(); 77 | xml.addAttribute!"href"(link); 78 | xml.endAttributesAndTag(); 79 | } 80 | 81 | xml.startTagWithAttributes!"content"(); 82 | xml.addAttribute!"type"("html"); 83 | xml.endAttributes(); 84 | xml.text(contentHtml); 85 | xml.endTag!"content"(); 86 | 87 | xml.endTag!"entry"(); 88 | } 89 | 90 | /// Finish writing. 91 | void endFeed() 92 | { 93 | xml.endTag!"feed"(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /utils/fps.d: -------------------------------------------------------------------------------- 1 | /** 2 | * FPS counter 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.fps; 15 | 16 | import std.datetime; 17 | import std.string; 18 | 19 | /// FPS counter. Calls a user-supplied delegate with a formatted string. 20 | struct FPSCounter 21 | { 22 | /// Update. Once a second, calls `setter` with a string containing 23 | /// the number of `tick` calls during the last second. 24 | void tick(void delegate(string) setter) 25 | { 26 | auto thisSecond = Clock.currTime().second; 27 | if (thisSecond != lastSecond) 28 | { 29 | setter(format("%03d (%d us)", frames, frames?1_000_000/frames:0)); 30 | frames = 0; 31 | lastSecond = thisSecond; 32 | } 33 | frames++; 34 | } 35 | 36 | private: 37 | uint frames, lastSecond; 38 | } 39 | -------------------------------------------------------------------------------- /utils/functor/composition.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Functor composition. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.functor.composition; 15 | 16 | import ae.utils.functor.primitives; 17 | 18 | import std.functional : forward; 19 | import std.meta : allSatisfy; 20 | import std.traits : isCallable; 21 | 22 | /// Check if `f` is a functor, and can participate in functor composition. 23 | // Work around https://issues.dlang.org/show_bug.cgi?id=20246 24 | // (We assume opCall is always a function or function template.) 25 | enum isFunctor(f...) = f.length == 1 && ( 26 | isCallable!f || __traits(hasMember, f, "opCall") 27 | ); 28 | 29 | debug(ae_unittest) unittest 30 | { 31 | static assert(isFunctor!(typeof(() => 5))); 32 | int i; 33 | static assert(isFunctor!(typeof(() => i))); 34 | auto getFive = functor!(() => 5)(); 35 | static assert(isFunctor!getFive); 36 | } 37 | 38 | /// The ternary operation using functors. 39 | template select(Cond, T, F) 40 | if (isFunctor!Cond && isFunctor!T && isFunctor!F) 41 | { 42 | static auto fun(Args...)(Cond cond, T t, F f, auto ref Args args) 43 | { 44 | return cond() 45 | ? t(forward!args) 46 | : f(forward!args); 47 | } 48 | 49 | auto select(Cond cond, T t, F f) @nogc 50 | { 51 | return functor!fun(cond, t, f); 52 | } 53 | } 54 | 55 | auto select(T, F)(bool cond, T t, F f) @nogc 56 | if (isFunctor!T && isFunctor!F) 57 | { return select(cond.valueFunctor, t, f); } /// ditto 58 | 59 | /// 60 | debug(ae_unittest) unittest 61 | { 62 | assert(select(true , 5.valueFunctor, 7.valueFunctor)() == 5); 63 | assert(select(false, 5.valueFunctor, 7.valueFunctor)() == 7); 64 | } 65 | 66 | /// The chain operation using functors. 67 | /// Calls all functors in sequence, returns `void`. 68 | /// (Not to be confused with function composition.) 69 | template seq(Functors...) 70 | if (allSatisfy!(isFunctor, Functors)) 71 | { 72 | static void fun(Args...)(ref Functors functors, auto ref Args args) 73 | { 74 | /*static*/ foreach (ref functor; functors) 75 | functor(args); 76 | } 77 | 78 | auto seq(Functors functors) @nogc 79 | { 80 | return functor!fun(functors); 81 | } 82 | } 83 | 84 | /// 85 | debug(ae_unittest) unittest 86 | { 87 | auto addFive = functor!(p => *p += 5)(); 88 | auto addThree = functor!(p => *p += 3)(); 89 | auto addEight = seq(addFive, addThree); 90 | int i; 91 | addEight(&i); 92 | assert(i == 8); 93 | } 94 | -------------------------------------------------------------------------------- /utils/functor/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Functor package. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.functor; 15 | 16 | public import ae.utils.functor.algorithm; 17 | public import ae.utils.functor.composition; 18 | public import ae.utils.functor.primitives; 19 | -------------------------------------------------------------------------------- /utils/graphics/bitmap.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Windows Bitmap definitions. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.graphics.bitmap; 15 | 16 | /// Additional definitions for types used in the BMP header. 17 | version (all) 18 | { 19 | alias int FXPT2DOT30; 20 | struct CIEXYZ { FXPT2DOT30 ciexyzX, /***/ ciexyzY, /***/ ciexyzZ; /***/ } 21 | struct CIEXYZTRIPLE { CIEXYZ ciexyzRed, /***/ciexyzGreen, /***/ciexyzBlue; /***/ } 22 | enum { BI_BITFIELDS = 3 /***/ } 23 | } 24 | 25 | /// Instantiates to a struct representing 26 | /// a BMP header at the given version. 27 | align(1) 28 | struct BitmapHeader(uint V) 29 | { 30 | enum VERSION = V; /// 31 | 32 | align(1): 33 | /// BITMAPFILEHEADER 34 | version (all) 35 | { 36 | char[2] bfType = "BM"; 37 | uint bfSize; 38 | ushort bfReserved1; 39 | ushort bfReserved2; 40 | uint bfOffBits; 41 | } 42 | 43 | /// BITMAPCOREINFO 44 | version (all) 45 | { 46 | uint bcSize = this.sizeof - bcSize.offsetof; 47 | int bcWidth; 48 | int bcHeight; 49 | ushort bcPlanes; 50 | ushort bcBitCount; 51 | uint biCompression; 52 | uint biSizeImage; 53 | uint biXPelsPerMeter; 54 | uint biYPelsPerMeter; 55 | uint biClrUsed; 56 | uint biClrImportant; 57 | } 58 | 59 | /// BITMAPV4HEADER 60 | static if (V>=4) 61 | { 62 | uint bV4RedMask; 63 | uint bV4GreenMask; 64 | uint bV4BlueMask; 65 | uint bV4AlphaMask; 66 | uint bV4CSType; 67 | CIEXYZTRIPLE bV4Endpoints; 68 | uint bV4GammaRed; 69 | uint bV4GammaGreen; 70 | uint bV4GammaBlue; 71 | } 72 | 73 | /// BITMAPV5HEADER 74 | static if (V>=5) 75 | { 76 | uint bV5Intent; 77 | uint bV5ProfileData; 78 | uint bV5ProfileSize; 79 | uint bV5Reserved; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /utils/graphics/fonts/draw.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Draw a bitmap font. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.graphics.fonts.draw; 15 | 16 | import ae.utils.graphics.draw; 17 | import ae.utils.graphics.view; 18 | 19 | /// Draw text using a bitmap font. 20 | void drawText(V, FONT, S, COLOR)(auto ref V v, xy_t x, xy_t y, S s, ref FONT font, COLOR color) 21 | if (isWritableView!V && is(COLOR : ViewColor!V)) 22 | { 23 | auto x0 = x; 24 | foreach (c; s) 25 | { 26 | if (c == '\r') 27 | x = x0; 28 | else 29 | if (c == '\n') 30 | { 31 | x = x0; 32 | y += font.height; 33 | } 34 | else 35 | { 36 | auto glyph = font.getGlyph(font.hasGlyph(c) ? c : ' '); 37 | foreach (cy; 0..font.height) 38 | foreach (cx; 0..glyph.width) 39 | if (glyph.rows[cy] & (1 << cx)) 40 | v.safePut(x+cx, y+cy, color); 41 | x += glyph.width; 42 | } 43 | } 44 | } 45 | 46 | debug(ae_unittest) 47 | { 48 | import ae.utils.graphics.image; 49 | import ae.utils.graphics.fonts.font8x8; 50 | } 51 | 52 | debug(ae_unittest) unittest 53 | { 54 | auto v = Image!ubyte(100, 8); 55 | v.drawText(0, 0, "Hello World!", font8x8, ubyte(255)); 56 | //v.toPNG.toFile("test.png"); 57 | } 58 | -------------------------------------------------------------------------------- /utils/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | * A sensible main() function. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.main; 15 | 16 | /** 17 | * Mix in a main function, which should be adequate 18 | * for most end-user programs. 19 | * 20 | * In debug mode (-debug), this is a pass-through. 21 | * Otherwise, this will catch uncaught exceptions, 22 | * and display only the message (sans stack trace) 23 | * to the user - on standard error, or, for Windows 24 | * GUI programs, in a message box. 25 | */ 26 | mixin template main(alias realMain) 27 | { 28 | version (unittest_only) 29 | { 30 | shared static this() 31 | { 32 | import core.runtime : Runtime, UnitTestResult; 33 | Runtime.extendedModuleUnitTester = { 34 | foreach (m; ModuleInfo) 35 | if (m) 36 | if (auto fp = m.unitTest) 37 | fp(); 38 | return UnitTestResult(); 39 | }; 40 | } 41 | } 42 | else 43 | int main(string[] args) 44 | { 45 | int run(string[] args) 46 | { 47 | static if (is(typeof(realMain()))) 48 | static if (is(typeof(realMain()) == void)) 49 | { realMain(); return 0; } 50 | else 51 | return realMain(); 52 | else 53 | static if (is(typeof(realMain(args)) == void)) 54 | { realMain(args); return 0; } 55 | else 56 | return realMain(args); 57 | } 58 | 59 | int runCatchingException(E, string message)(string[] args) 60 | { 61 | try 62 | return run(args); 63 | catch (E e) 64 | { 65 | version (Windows) 66 | { 67 | import core.sys.windows.windows; 68 | auto h = GetStdHandle(STD_ERROR_HANDLE); 69 | if (!h || h == INVALID_HANDLE_VALUE) 70 | { 71 | import ae.sys.windows : messageBox; 72 | messageBox(e.msg, message, MB_ICONERROR); 73 | return 1; 74 | } 75 | } 76 | 77 | import std.stdio : stderr; 78 | stderr.writefln("%s: %s", message, e.msg); 79 | return 1; 80 | } 81 | } 82 | 83 | debug 84 | static if(is(std.getopt.GetOptException)) 85 | return runCatchingException!(std.getopt.GetOptException, "Usage error")(args); 86 | else 87 | return run(args); 88 | else 89 | return runCatchingException!(Throwable, "Fatal error")(args); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /utils/mapset/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Package module. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.mapset; 15 | 16 | public import ae.utils.mapset.mapset; 17 | public import ae.utils.mapset.visitor; 18 | -------------------------------------------------------------------------------- /utils/meta/binding.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Method binding - using alias inference patch 3 | * Currently dysfunctional. 4 | * 5 | * License: 6 | * This Source Code Form is subject to the terms of 7 | * the Mozilla Public License, v. 2.0. If a copy of 8 | * the MPL was not distributed with this file, You 9 | * can obtain one at http://mozilla.org/MPL/2.0/. 10 | * 11 | * Authors: 12 | * Vladimir Panteleev 13 | */ 14 | 15 | module ae.utils.meta.binding; 16 | 17 | import ae.utils.meta : thisOf; 18 | import ae.utils.meta.caps; 19 | import ae.utils.meta.reference; 20 | 21 | /// TODO: update to use the new __traits(child) 22 | 23 | deprecated: 24 | 25 | /// Create unbound functor of a method 26 | template unboundFunctorOf(alias f) 27 | { 28 | static @property auto unboundFunctorOf() 29 | { 30 | UnboundFunctorOf!f r; 31 | return r; 32 | } 33 | } 34 | struct UnboundFunctorOf(alias f) 35 | { 36 | alias opCall = f; 37 | 38 | alias R = RefType!(thisOf!f); 39 | auto bind(R r) { return boundFunctorOf!f(r); } 40 | } /// ditto 41 | 42 | /// Create bound functor of a method 43 | template boundFunctorOf(alias f) 44 | { 45 | static @property auto boundFunctorOf(T)(T context) 46 | { 47 | BoundFunctorOf!(T, f) r; 48 | r.context = context; 49 | return r; 50 | } 51 | } 52 | 53 | /// ditto 54 | @property auto boundFunctorOf(alias f)() 55 | if (is(typeof(this))) // haveMethodAliasBinding 56 | { 57 | BoundFunctorOf!(RefType!(typeof(this)), f) r; 58 | r.context = this.reference; 59 | return r; 60 | } 61 | 62 | struct BoundFunctorOf(R, alias f) 63 | { 64 | R context; 65 | template opCall(Args...) 66 | { 67 | alias Ret = typeof(__traits(child, context, f)(Args.init)); 68 | Ret opCall(auto ref Args args) 69 | { 70 | return __traits(child, context, f)(args); 71 | } 72 | } 73 | 74 | /// Ignore - BoundFunctors are already bound 75 | auto bind(R)(R r) { return this; } 76 | } 77 | 78 | static if (haveChildTrait) 79 | debug(ae_unittest) unittest 80 | { 81 | static struct Test 82 | { 83 | void caller(Func)(Func func) 84 | { 85 | func(); 86 | } 87 | 88 | int i = 0; 89 | 90 | void callee() 91 | { 92 | i++; 93 | } 94 | 95 | void test() 96 | { 97 | caller(unboundFunctorOf!callee.bind(&this)); 98 | assert(i == 1); 99 | 100 | static if (haveMethodAliasBinding) // or is it haveAliasCtxInference ? 101 | { 102 | caller(unboundFunctorOf!callee); 103 | caller( boundFunctorOf!callee); 104 | 105 | assert(i == 3); 106 | } 107 | 108 | static struct S 109 | { 110 | int i = 0; 111 | 112 | void callee() 113 | { 114 | i++; 115 | } 116 | } 117 | S s; 118 | caller(boundFunctorOf!(S.callee)(&s)); 119 | 120 | assert(s.i == 1); 121 | } 122 | } 123 | 124 | Test test; 125 | test.test(); 126 | } 127 | -------------------------------------------------------------------------------- /utils/meta/misc.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Metaprogramming miscellanea 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | deprecated module ae.utils.meta.misc; 15 | 16 | public import ae.utils.meta; 17 | -------------------------------------------------------------------------------- /utils/meta/reference.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Reference type abstraction 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.meta.reference; 15 | 16 | import std.traits; 17 | 18 | /// `typeof(new T)` - what we use to refer to an allocated instance 19 | template RefType(T) 20 | { 21 | /// 22 | static if (is(T == class)) 23 | alias T RefType; 24 | else 25 | alias T* RefType; 26 | } 27 | 28 | /// Reverse of `RefType` 29 | template FromRefType(R) 30 | { 31 | /// 32 | static if (is(T == class)) 33 | alias T FromRefType; 34 | else 35 | { 36 | static assert(is(typeof(*(R.init))), R.stringof ~ " is not dereferenceable"); 37 | alias typeof(*(R.init)) FromRefType; 38 | } 39 | } 40 | 41 | /// A type that can be used to store instances of T. 42 | /// A struct with T's instance size if T is a class, T itself otherwise. 43 | template StorageType(T) 44 | { 45 | /// 46 | static if (is(T == class)) 47 | { 48 | //alias void*[(__traits(classInstanceSize, T) + size_t.sizeof-1) / size_t.sizeof] StorageType; 49 | //static assert(__traits(classInstanceSize, T) % size_t.sizeof == 0, "TODO"); // union with a pointer 50 | 51 | // Use a struct to allow new-ing the type (you can't new a static array directly) 52 | struct StorageType 53 | { 54 | void*[(__traits(classInstanceSize, T) + size_t.sizeof-1) / size_t.sizeof] data; /// Class data store. 55 | } 56 | } 57 | else 58 | alias T StorageType; 59 | } 60 | 61 | // ************************************************************************ 62 | 63 | /// Is T a reference type (a pointer or a class)? 64 | template isReference(T) 65 | { 66 | enum isReference = isPointer!T || is(T==class); 67 | } 68 | 69 | /// Allow passing a constructed object by reference, without redundant indirection. 70 | /// The intended use is with types which support the dot operator 71 | /// (a non-null class, a struct, or a non-null struct pointer). 72 | T* reference(T)(ref T v) 73 | if (!isReference!T) 74 | { 75 | return &v; 76 | } 77 | 78 | /// ditto 79 | T reference(T)(T v) 80 | if (isReference!T) 81 | { 82 | return v; 83 | } 84 | 85 | /// Reverse of "reference". 86 | ref typeof(*T.init) dereference(T)(T v) 87 | if (!isReference!T) 88 | { 89 | return *v; 90 | } 91 | 92 | /// ditto 93 | T dereference(T)(T v) 94 | if (isReference!T) 95 | { 96 | return v; 97 | } 98 | 99 | debug(ae_unittest) unittest 100 | { 101 | Object o = new Object; 102 | assert(o.reference is o); 103 | assert(o.dereference is o); 104 | 105 | static struct S {} 106 | S s; 107 | auto p = s.reference; 108 | assert(p is &s); 109 | assert(p.reference is p); 110 | } 111 | -------------------------------------------------------------------------------- /utils/meta/x.d: -------------------------------------------------------------------------------- 1 | /** 2 | * An implementation of Timon Gehr's X template 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.meta.x; 15 | 16 | // Based on idea from Timon Gehr. 17 | // http://forum.dlang.org/post/jdiu5s$13bo$1@digitalmars.com 18 | 19 | /// Pre-processes a D code mixin, allowing 20 | /// interpolation using @(...) sequences. 21 | template X(string x) 22 | { 23 | enum X = xImpl(x); 24 | } 25 | 26 | private string xImpl(string x) 27 | { 28 | string r; 29 | 30 | for (size_t i=0; i 12 | */ 13 | 14 | module ae.utils.mime; 15 | 16 | import std.string; 17 | import std.path; 18 | 19 | /// Return a likely MIME type for a file with the given name. 20 | string guessMime(string fileName, string defaultResult = null) 21 | { 22 | string ext = toLower(extension(fileName)); 23 | 24 | if (ext.endsWith("-opt")) 25 | ext = ext[0..$-4]; // HACK 26 | 27 | switch (ext) 28 | { 29 | case ".txt": 30 | return "text/plain"; 31 | case ".htm": 32 | case ".html": 33 | return "text/html"; 34 | case ".js": 35 | return "text/javascript"; 36 | case ".json": 37 | return "application/json"; 38 | case ".wasm": 39 | return "application/wasm"; 40 | case ".css": 41 | return "text/css"; 42 | case ".png": 43 | return "image/png"; 44 | case ".gif": 45 | return "image/gif"; 46 | case ".jpg": 47 | case ".jpeg": 48 | return "image/jpeg"; 49 | case ".svg": 50 | return "image/svg+xml"; 51 | case ".ico": 52 | return "image/vnd.microsoft.icon"; 53 | case ".swf": 54 | return "application/x-shockwave-flash"; 55 | case ".wav": 56 | return "audio/wav"; 57 | case ".mp3": 58 | return "audio/mpeg"; 59 | case ".webm": 60 | return "video/webm"; 61 | case ".mp4": 62 | return "video/mp4"; 63 | 64 | case ".c": 65 | return "text/x-csrc"; 66 | case ".h": 67 | return "text/x-chdr"; 68 | case ".cpp": 69 | case ".c++": 70 | case ".cxx": 71 | case ".cc": 72 | return "text/x-c++src"; 73 | case ".hpp": 74 | case ".h++": 75 | case ".hxx": 76 | case ".hh": 77 | return "text/x-c++hdr"; 78 | case ".d": // by extension :P 79 | return "text/x-dsrc"; 80 | case ".di": 81 | return "text/x-dhdr"; 82 | 83 | // https://pki-tutorial.readthedocs.io/en/latest/mime.html 84 | 85 | case ".p8": 86 | // case ".key": 87 | return "application/pkcs8"; 88 | case ".p10": 89 | // case ".csr": 90 | return "application/pkcs10"; 91 | // case ".cer": 92 | // return "application/pkix-cert"; 93 | // case ".crl": 94 | // return "application/pkix-crl"; 95 | case ".p7c": 96 | return "application/pkcs7-mime"; 97 | 98 | // case ".crt": 99 | // case ".der": 100 | // return "application/x-x509-ca-cert"; 101 | // case ".crt": 102 | // return "application/x-x509-user-cert"; 103 | // case ".crl": 104 | // return "application/x-pkcs7-crl"; 105 | 106 | case ".pem": 107 | return "application/x-pem-file"; 108 | case ".p12": 109 | case ".pfx": 110 | return "application/x-pkcs12"; 111 | 112 | case ".p7b": 113 | case ".spc": 114 | return "application/x-pkcs7-certificates"; 115 | case ".p7r": 116 | return "application/x-pkcs7-certreqresp"; 117 | 118 | default: 119 | return defaultResult; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /utils/pred/algorithm.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Renamed to ae.utils.functor 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | deprecated module ae.utils.pred.algorithm; 15 | 16 | public import ae.utils.functor.primitives; 17 | public import ae.utils.functor.algorithm; 18 | 19 | deprecated alias pred = functor; 20 | deprecated alias pmap = map; 21 | deprecated alias pfilter = filter; 22 | -------------------------------------------------------------------------------- /utils/promise/range.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Promise range tools. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.promise.range; 15 | 16 | import std.range.primitives; 17 | 18 | import ae.net.asockets : socketManager; 19 | import ae.utils.promise; 20 | 21 | /// Given a range of promises, resolve them one after another, 22 | /// and return a promise which is fulfilled when all promises in `range` are fulfilled. 23 | /// `range` may be a lazy range (e.g. a `map` which produces promises from other input), 24 | /// which will cause the work to be started only when the previous promise completes. 25 | PromiseValueTransform!(ElementType!R, x => [x]) allSerial(R)(R range) 26 | if (isInputRange!R) 27 | { 28 | auto p = new typeof(return); 29 | 30 | alias P = ElementType!R; 31 | alias T = PromiseValue!P; 32 | alias E = PromiseError!P; 33 | 34 | typeof(p).ValueTuple results; 35 | static if (!is(T == void)) 36 | { 37 | static if (hasLength!R) 38 | results[0].reserve(range.length); 39 | } 40 | 41 | void next() 42 | { 43 | if (range.empty) 44 | p.fulfill(results); 45 | else 46 | { 47 | range.front.then((P.ValueTuple value) { 48 | static if (!is(T == void)) 49 | results[0] ~= value[0]; 50 | next(); 51 | }, (E error) { 52 | p.reject(error); 53 | }); 54 | range.popFront(); 55 | } 56 | } 57 | 58 | next(); 59 | 60 | return p; 61 | } 62 | 63 | debug(ae_unittest) unittest 64 | { 65 | import std.algorithm.iteration : map; 66 | import ae.sys.timing : setTimeout; 67 | import core.time : seconds; 68 | 69 | size_t sum; 70 | [1, 2, 3] 71 | .map!((n) { 72 | auto nextSum = sum + n; 73 | auto p = new Promise!void(); 74 | setTimeout({ sum = nextSum; p.fulfill(); }, 0.seconds); 75 | return p; 76 | }) 77 | .allSerial; 78 | socketManager.loop(); 79 | assert(sum == 6); 80 | } 81 | -------------------------------------------------------------------------------- /utils/promise/timing.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Promise-based timing utilities. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.promise.timing; 15 | 16 | import ae.utils.promise; 17 | import ae.sys.timing; 18 | 19 | Promise!void sleep(Duration delay) 20 | { 21 | auto p = new Promise!void; 22 | setTimeout(&p.fulfill, delay); 23 | return p; 24 | } 25 | 26 | Promise!void sleepUntil(MonoTime when) 27 | { 28 | auto p = new Promise!void; 29 | auto task = new TimerTask((Timer /*timer*/, TimerTask /*task*/) { 30 | p.fulfill(); 31 | }); 32 | mainTimer.add(task, when); 33 | return p; 34 | } 35 | -------------------------------------------------------------------------------- /utils/random.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.utils.random 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.random; 15 | 16 | import std.algorithm.comparison; 17 | import std.algorithm.mutation; 18 | import std.array; 19 | import std.random; 20 | import std.range.primitives; 21 | 22 | /// Like `randomShuffle`, but returns results incrementally 23 | /// (still copies the input, but calls `gen` only as needed). 24 | /// Like `randomCover`, but much faster 25 | /// (O(n) instead of O(n^2), though less space-efficient. 26 | auto incrementalRandomShuffle(Range, RandomGen)(Range range, ref RandomGen gen) 27 | if (isInputRange!Range && isUniformRNG!RandomGen) 28 | { 29 | alias E = ElementType!Range; 30 | static struct IncrementalRandomShuffle 31 | { 32 | private: 33 | E[] arr; 34 | size_t i; 35 | RandomGen* gen; 36 | 37 | this(Range range, RandomGen* gen) 38 | { 39 | this.arr = range.array; 40 | this.gen = gen; 41 | prime(); 42 | } 43 | 44 | void prime() 45 | { 46 | import std.algorithm.mutation : swapAt; 47 | arr.swapAt(i, i + uniform(0, arr.length - i, gen)); 48 | } 49 | 50 | public: 51 | @property bool empty() const { return i == arr.length; } 52 | ref E front() { return arr[i]; } 53 | void popFront() 54 | { 55 | i++; 56 | if (!empty) 57 | prime(); 58 | } 59 | } 60 | 61 | return IncrementalRandomShuffle(move(range), &gen); 62 | } 63 | 64 | auto incrementalRandomShuffle(Range)(Range range) 65 | if (isInputRange!Range) 66 | { return incrementalRandomShuffle(range, rndGen); } 67 | 68 | debug(ae_unittest) unittest 69 | { 70 | auto shuffled = [1, 2].incrementalRandomShuffle; 71 | assert(shuffled.equal([1, 2]) || shuffled.equal([2, 1])); 72 | } 73 | -------------------------------------------------------------------------------- /utils/sound/asound.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Play waves using ALSA command-line tools 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.asound; 15 | 16 | import std.conv; 17 | import std.exception; 18 | import std.process; 19 | import std.range; 20 | import std.traits; 21 | 22 | /// Return the ALSA format name corresponding to the given type. 23 | template aSoundFormat(T) 24 | { 25 | /// 26 | version(LittleEndian) 27 | enum aSoundEndianness = "_LE"; 28 | else 29 | enum aSoundEndianness = "_BE"; 30 | 31 | /// 32 | static if (is(T==ubyte)) 33 | enum aSoundFormat = "U8"; 34 | else 35 | static if (is(T==byte)) 36 | enum aSoundFormat = "S8"; 37 | else 38 | static if (is(T==ushort)) 39 | enum aSoundFormat = "U16" ~ aSoundEndianness; 40 | else 41 | static if (is(T==short)) 42 | enum aSoundFormat = "S16" ~ aSoundEndianness; 43 | else 44 | static if (is(T==uint)) 45 | enum aSoundFormat = "U32" ~ aSoundEndianness; 46 | else 47 | static if (is(T==int)) 48 | enum aSoundFormat = "S32" ~ aSoundEndianness; 49 | else 50 | static if (is(T==float)) 51 | enum aSoundFormat = "FLOAT" ~ aSoundEndianness; 52 | else 53 | static if (is(T==double)) 54 | enum aSoundFormat = "FLOAT64" ~ aSoundEndianness; 55 | else 56 | static assert(false, "Can't represent sample type in asound format: " ~ T.stringof); 57 | } 58 | 59 | /// Play a wave (range of samples) using `aplay`. 60 | void playWave(Wave)(Wave wave, int sampleRate = 44100) 61 | { 62 | alias Sample = typeof(wave.front); 63 | static if (is(Sample C : C[channels_], size_t channels_)) 64 | { 65 | alias ChannelSample = C; 66 | enum channels = channels_; 67 | } 68 | else 69 | { 70 | alias ChannelSample = Sample; 71 | enum channels = 1; 72 | } 73 | auto p = pipe(); 74 | auto pid = spawnProcess([ 75 | "aplay", 76 | "--format", aSoundFormat!ChannelSample, 77 | "--channels", text(channels), 78 | "--rate", text(sampleRate), 79 | ], p.readEnd()); 80 | while (!wave.empty) 81 | { 82 | Sample[1] s; 83 | s[0] = wave.front; 84 | wave.popFront(); 85 | p.writeEnd.rawWrite(s[]); 86 | } 87 | p.writeEnd.close(); 88 | enforce(pid.wait() == 0, "aplay failed"); 89 | } 90 | 91 | debug(ae_unittest) unittest 92 | { 93 | if (false) 94 | playWave(iota(100)); 95 | } 96 | -------------------------------------------------------------------------------- /utils/sound/riff/common.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Common code for the RIFF file format (used in .wav files). 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.riff.common; 15 | 16 | /// RIFF "fmt" chunk. 17 | struct WaveFmt 18 | { 19 | ushort format; /// 20 | ushort numChannels; /// 21 | uint sampleRate; /// 22 | uint byteRate; /// 23 | ushort blockAlign; /// 24 | ushort bitsPerSample; /// 25 | } 26 | -------------------------------------------------------------------------------- /utils/sound/riff/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Support for the RIFF file format (used in .wav files). 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.riff; 15 | 16 | public import ae.utils.sound.riff.reader; 17 | public import ae.utils.sound.riff.writer; 18 | -------------------------------------------------------------------------------- /utils/sound/riff/reader.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Read support for the RIFF file format (used in .wav files). 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.riff.reader; 15 | 16 | import std.algorithm.searching; 17 | import std.exception; 18 | 19 | import ae.utils.sound.riff.common; 20 | 21 | /// RIFF chunk. 22 | struct Chunk 23 | { 24 | /// Chunk header. 25 | struct Header 26 | { 27 | char[4] name; /// 28 | uint length; /// 29 | ubyte[0] data; /// Chunk data follows. 30 | } 31 | 32 | Header* header; /// ditto 33 | 34 | /// Parse and consume one chunk from `data`. 35 | this(ref ubyte[] data) 36 | { 37 | enforce(data.length >= Header.sizeof); 38 | header = cast(Header*)data; 39 | data = data[Header.sizeof..$]; 40 | 41 | enforce(data.length >= header.length); 42 | data = data[header.length..$]; 43 | } 44 | 45 | char[4] name() { return header.name; } /// Chunk name (from header). 46 | ubyte[] data() { return header.data.ptr[0..header.length]; } /// Chunk data. 47 | } 48 | 49 | /// Reads chunks from an array of bytes. 50 | struct Chunks 51 | { 52 | ubyte[] data; /// The array of bytes. 53 | /// Range primitives. 54 | bool empty() { return data.length == 0; } 55 | Chunk front() { auto cData = data; return Chunk(cData); } /// ditto 56 | void popFront() { auto c = Chunk(data); } /// ditto 57 | } 58 | 59 | /// Return a `Chunk` from `data`. 60 | auto readRiff(ubyte[] data) 61 | { 62 | return Chunk(data); 63 | } 64 | 65 | /// Get samples from a parsed RIFF file. 66 | /// The format is expected to match `T`. 67 | auto getWave(T)(Chunk chunk) 68 | { 69 | enforce(chunk.name == "RIFF", "Unknown file format"); 70 | auto riffData = chunk.data; 71 | enforce(riffData.skipOver("WAVE"), "Unknown RIFF contents"); 72 | WaveFmt fmt = (cast(WaveFmt[])Chunks(riffData).find!(c => c.name == "fmt ").front.data)[0]; 73 | enforce(fmt.format == 1, "Unknown WAVE format"); 74 | enforce(fmt.sampleRate * T.sizeof == fmt.byteRate, "Format mismatch"); 75 | auto data = Chunks(riffData).find!(c => c.name == "data").front.data; 76 | return cast(T[])data; 77 | } 78 | -------------------------------------------------------------------------------- /utils/sound/riff/writer.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Write support for the RIFF file format (used in .wav files). 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.riff.writer; 15 | 16 | import std.algorithm; 17 | import std.conv; 18 | import std.range; 19 | 20 | import ae.utils.sound.riff.common; 21 | import ae.utils.array : staticArray; 22 | 23 | private struct ValueReprRange(T) 24 | { 25 | ubyte[T.sizeof] bytes; 26 | size_t p; 27 | 28 | this(ref T t) 29 | { 30 | bytes[] = (cast(ubyte[])((&t)[0..1]))[]; 31 | } 32 | 33 | @property ubyte front() { return bytes[p]; } 34 | void popFront() { p++; } 35 | @property bool empty() { return p == T.sizeof; } 36 | @property size_t length() { return T.sizeof - p; } 37 | } 38 | 39 | private auto valueReprRange(T)(auto ref T t) { return ValueReprRange!T(t); } 40 | 41 | private auto fourCC(char[4] name) 42 | { 43 | return valueReprRange(name); 44 | } 45 | 46 | /// Serialize a chunk and data as a range of bytes. 47 | auto riffChunk(R)(char[4] name, R data) 48 | { 49 | return chain( 50 | fourCC(name), 51 | valueReprRange(data.length.to!uint), 52 | data 53 | ); 54 | } 55 | 56 | /// Serialize a range of samples into a range of bytes representing a RIFF file. 57 | auto makeRiff(R)(R r, uint sampleRate = 44100) 58 | { 59 | alias Sample = typeof(r.front); 60 | static if (!is(Sample C : C[channels_], size_t channels_)) 61 | return makeRiff(r.map!(s => [s].staticArray), sampleRate); 62 | else 63 | { 64 | enum numChannels = r.front.length; 65 | alias ChannelSample = typeof(r.front[0]); 66 | auto bytesPerSample = ChannelSample.sizeof; 67 | auto bitsPerSample = bytesPerSample * 8; 68 | static if (is(ChannelSample : long)) 69 | enum format = 1; // Integer PCM 70 | else 71 | static if (is(ChannelSample : real)) 72 | enum format = 3; // Floating-point PCM 73 | else 74 | static assert(false, "Unknown sample format: " ~ Sample.stringof); 75 | 76 | return riffChunk("RIFF", 77 | chain( 78 | fourCC("WAVE"), 79 | riffChunk("fmt ", 80 | valueReprRange(WaveFmt( 81 | format, 82 | to!ushort(numChannels), 83 | sampleRate, 84 | to!uint (sampleRate * bytesPerSample * numChannels), 85 | to!ushort(bytesPerSample * numChannels), 86 | to!ushort(bitsPerSample), 87 | )), 88 | ), 89 | riffChunk("data", 90 | r.map!(s => valueReprRange(s)).joiner.takeExactly(r.length * r.front.sizeof), 91 | ), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /utils/sound/wave.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Some simple wave generator functions. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.wave; 15 | 16 | import std.algorithm; 17 | import std.conv; 18 | import std.math; 19 | import std.range; 20 | 21 | import ae.utils.math; 22 | import ae.utils.range; 23 | 24 | enum sampleMax(T) = is(T : long) ? T.max : T(1); 25 | 26 | /// Simple wave generator. 27 | auto squareWave(T)(real interval) 28 | { 29 | return infiniteIota!size_t 30 | .map!(n => cast(T)(sampleMax!T + cast(int)(n * 2 / interval) % 2)); 31 | } 32 | 33 | /// ditto 34 | auto sawToothWave(T)(real interval) 35 | { 36 | return infiniteIota!size_t 37 | .map!(n => cast(T)((n % interval * 2 - interval) * sampleMax!T / interval)); 38 | } 39 | 40 | /// ditto 41 | auto triangleWave(T)(real interval) 42 | { 43 | return infiniteIota!size_t 44 | .map!(n => cast(T)((abs(n % interval * 2 - interval) * 2 - interval) * sampleMax!T / interval)); 45 | } 46 | 47 | /// ditto 48 | auto sineWave(T)(real interval) 49 | { 50 | return infiniteIota!size_t 51 | .map!(n => (sin(n * 2 * PI / interval) * sampleMax!T).to!T); 52 | } 53 | 54 | /// ditto 55 | auto whiteNoise(T)() 56 | { 57 | import std.random; 58 | return infiniteIota!size_t 59 | .map!(n => cast(T)Xorshift(cast(uint)n).front); 60 | } 61 | 62 | /// ditto 63 | auto whiteNoiseSqr(T)() 64 | { 65 | import std.random; 66 | return infiniteIota!size_t 67 | .map!(n => Xorshift(cast(uint)n).front % 2 ? sampleMax!T : T.min); 68 | } 69 | 70 | /// Fade out this wave (multiply samples by a linearly descending factor). 71 | auto fade(W)(W w) 72 | { 73 | alias T = typeof(w.front); 74 | sizediff_t dur = w.length; 75 | return dur.iota.map!(p => cast(T)(w[p] * (dur-p) / dur)); 76 | } 77 | 78 | /// Stretch a wave with linear interpolation. 79 | auto stretch(W)(W wave, real factor) 80 | { 81 | static if (is(typeof(wave.length))) 82 | { 83 | auto length = cast(size_t)(wave.length * factor); 84 | auto baseRange = length.iota; 85 | } 86 | else 87 | auto baseRange = infiniteIota!size_t; 88 | return baseRange 89 | .map!((n) { 90 | auto p = n / factor; 91 | auto ip = cast(size_t)p; 92 | return cast(typeof(wave.front))itpl(wave[ip], wave[ip+1], p, ip, ip+1); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /utils/sound/windows.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Play waves using the Windows multimedia API 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.sound.windows; 15 | version (Windows): 16 | 17 | import ae.utils.sound.riff.writer; 18 | 19 | import core.time; 20 | 21 | import std.algorithm; 22 | import std.range; 23 | 24 | import ae.sys.windows.imports; 25 | mixin(importWin32!q{mmsystem}); 26 | mixin(importWin32!q{winnt}); 27 | 28 | mixin(importWin32!(q{mmsystem}, q{public}, q{SND_ASYNC, SND_LOOP, SND_NOSTOP})); 29 | 30 | /// Play a wave using the Windows API. 31 | void playWave(Wave)(Wave wave, Duration duration, uint flags = 0) 32 | { 33 | enum sampleRate = 44100; 34 | auto samples = cast(size_t)(duration.total!"hnsecs" * sampleRate / convert!("seconds", "hnsecs")(1)); 35 | playRiff(makeRiff(wave.takeExactly(samples), sampleRate), flags); 36 | } 37 | 38 | /// Play a RIFF (range of bytes) using the Windows API. 39 | void playRiff(Riff)(Riff riff, uint flags = 0) 40 | { 41 | import core.stdc.stdlib : malloc, free; 42 | 43 | auto data = (cast(ubyte*)malloc(riff.length))[0..riff.length]; 44 | scope(exit) free(data.ptr); 45 | copy(riff, data); 46 | PlaySoundA(cast(LPCSTR)data.ptr, null, SND_MEMORY | flags); 47 | } 48 | -------------------------------------------------------------------------------- /utils/text/csv.d: -------------------------------------------------------------------------------- 1 | /** 2 | * CSV writing / formatting. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.text.csv; 15 | 16 | import std.algorithm.searching; 17 | import std.array; 18 | import std.exception; 19 | import std.utf; 20 | 21 | import ae.utils.aa; 22 | 23 | void toCSV(Output)(OrderedMap!(string, string)[] rows, Output output) 24 | { 25 | void putValue(string value) 26 | { 27 | if (value.empty || value.byChar.any!(c => c == '"' || c == '\'' || c == '\n' || c == '\r' || c == ',' || c == ';' || c == ' ' || c == '\t')) 28 | { 29 | output.put('"'); 30 | foreach (c; value) 31 | { 32 | if (c == '"') 33 | output.put('"'); 34 | output.put(c); 35 | } 36 | output.put('"'); 37 | } 38 | else 39 | output.put(value); 40 | } 41 | 42 | enforce(rows.length > 0, "Cannot write empty CSV"); 43 | 44 | { 45 | bool first = true; 46 | foreach (header; rows[0].byKey) 47 | { 48 | if (first) 49 | first = false; 50 | else 51 | output.put(','); 52 | putValue(header); 53 | } 54 | output.put("\r\n"); 55 | } 56 | 57 | foreach (row; rows) 58 | { 59 | bool first = true; 60 | foreach (name, value; row) 61 | { 62 | if (first) 63 | first = false; 64 | else 65 | output.put(','); 66 | putValue(value); 67 | } 68 | output.put("\r\n"); 69 | } 70 | } 71 | 72 | string toCSV(OrderedMap!(string, string)[] rows) 73 | { 74 | auto buffer = appender!string; 75 | toCSV(rows, buffer); 76 | return buffer.data; 77 | } 78 | -------------------------------------------------------------------------------- /utils/text/html.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.utils.text.html 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.text.html; 15 | 16 | import ae.utils.textout; 17 | 18 | /// Encodes special HTML characters (`'<'`, `'>'`, `'&'`) into HTML entities. 19 | /// If `inAttribute` is `true`, `'"'` and `'\''` are also encoded. 20 | string encodeHtmlEntities(bool inAttribute = true)(string s) 21 | { 22 | StringBuilder result; 23 | size_t start = 0; 24 | 25 | foreach (i, c; s) 26 | if (c=='<') 27 | result.put(s[start..i], "<"), 28 | start = i+1; 29 | else 30 | if (c=='>') 31 | result.put(s[start..i], ">"), 32 | start = i+1; 33 | else 34 | if (c=='&') 35 | result.put(s[start..i], "&"), 36 | start = i+1; 37 | else 38 | if (inAttribute && c=='"') 39 | result.put(s[start..i], """), 40 | start = i+1; 41 | else 42 | if (inAttribute && c=='\'') 43 | result.put(s[start..i], "'"), 44 | start = i+1; 45 | 46 | if (!start) 47 | return s; 48 | 49 | result.put(s[start..$]); 50 | return result.get(); 51 | } 52 | -------------------------------------------------------------------------------- /utils/time/fpdur.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Duration functions. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.time.fpdur; 15 | 16 | import core.time; 17 | 18 | import ae.utils.time.types : AbsTime; 19 | 20 | /// A variant of core.time.dur which accepts floating-point values. 21 | /// Useful for parsing command-line arguments. 22 | /// Beware of rounding / floating-point errors! Do not use where precision matters. 23 | template dur(string units) 24 | if (units == "weeks" || 25 | units == "days" || 26 | units == "hours" || 27 | units == "minutes" || 28 | units == "seconds" || 29 | units == "msecs" || 30 | units == "usecs" || 31 | units == "hnsecs" || 32 | units == "nsecs") 33 | { 34 | Duration dur(T)(T length) @safe pure nothrow @nogc 35 | if (is(T : real) && !is(T : ulong)) 36 | { 37 | auto hnsecs = length * convert!(units, "hnsecs")(1); 38 | // https://issues.dlang.org/show_bug.cgi?id=15900 39 | static import core.time; 40 | return core.time.hnsecs(cast(long)hnsecs); 41 | } 42 | } 43 | 44 | alias weeks = dur!"weeks"; /// Ditto 45 | alias days = dur!"days"; /// Ditto 46 | alias hours = dur!"hours"; /// Ditto 47 | alias minutes = dur!"minutes"; /// Ditto 48 | alias seconds = dur!"seconds"; /// Ditto 49 | alias msecs = dur!"msecs"; /// Ditto 50 | alias usecs = dur!"usecs"; /// Ditto 51 | alias hnsecs = dur!"hnsecs"; /// Ditto 52 | alias nsecs = dur!"nsecs"; /// Ditto 53 | 54 | /// 55 | debug(ae_unittest) unittest 56 | { 57 | import core.time : msecs; 58 | static assert(1.5.seconds == 1500.msecs); 59 | } 60 | 61 | /// Multiply a duration by a floating-point number. 62 | Duration durScale(F)(Duration d, F f) 63 | if (is(F : real)) 64 | { 65 | return hnsecs(d.total!"hnsecs" * f); 66 | } 67 | 68 | /// 69 | debug(ae_unittest) unittest 70 | { 71 | import core.time : seconds, msecs; 72 | assert(durScale(1.seconds, 1.5) == 1500.msecs); 73 | } 74 | 75 | /// Like d.total!units, but returns fractional parts as well. 76 | T fracTotal(string units, T = real)(Duration d) 77 | { 78 | return T(d.total!"hnsecs") / convert!(units, "hnsecs")(1); 79 | } 80 | 81 | /// 82 | debug(ae_unittest) unittest 83 | { 84 | import core.time : seconds, msecs; 85 | assert(1500.msecs.fracTotal!"seconds" == 1.5); 86 | } 87 | 88 | AbsTime fromUnixTime(double unixTime) 89 | { 90 | import std.datetime.systime : SysTime; 91 | import std.datetime.timezone : UTC; 92 | 93 | auto durationSinceUnixEpoch = unixTime.seconds; 94 | enum stdTimeEpoch = SysTime.fromUnixTime(0, UTC()).stdTime; 95 | return AbsTime(stdTimeEpoch) + durationSinceUnixEpoch; 96 | } 97 | -------------------------------------------------------------------------------- /utils/time/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Time string formatting and such. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.time; 15 | 16 | public import ae.utils.time.caldur; 17 | public import ae.utils.time.common; 18 | public import ae.utils.time.format; 19 | public import ae.utils.time.fpdur; 20 | public import ae.utils.time.parse; 21 | public import ae.utils.time.parsedur; 22 | public import ae.utils.time.types; 23 | 24 | debug(ae_unittest) unittest 25 | { 26 | import core.stdc.time : time_t; 27 | 28 | enum f = `U\.9`; 29 | static if (time_t.sizeof == 4) 30 | assert("1234567890.123456789".parseTime!f.formatTime!f == "1234567890.123456700"); 31 | else 32 | assert("123456789012.123456789".parseTime!f.formatTime!f == "123456789012.123456700"); 33 | } 34 | 35 | // *************************************************************************** 36 | 37 | // fpdur conflict test 38 | debug(ae_unittest) unittest 39 | { 40 | import std.datetime; 41 | import ae.utils.time.fpdur; 42 | static assert(1.5.seconds == 1500.msecs); 43 | } 44 | -------------------------------------------------------------------------------- /utils/time/parsedur.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Duration parsing functions. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.time.parsedur; 15 | 16 | import core.time; 17 | 18 | import std.algorithm.iteration : filter; 19 | import std.array; 20 | import std.ascii; 21 | import std.conv; 22 | import std.exception; 23 | import std.string; 24 | 25 | /// Parse a duration string in the form returned by Duration.toString 26 | /// (e.g. "1 day, 1 hour and 30 minutes") 27 | Duration parseDuration(string s) 28 | { 29 | s = s.replace(" and ", " "); 30 | s = s.replace(", ", " "); 31 | auto words = std.string.split(s).filter!(word => word.length); 32 | enforce(!words.empty, "No duration given"); 33 | 34 | Duration result; 35 | 36 | while (!words.empty) 37 | { 38 | auto word = words.front; 39 | words.popFront(); 40 | assert(word.length); 41 | enforce(word[0].isDigit || word[0] == '-', "Digit expected: " ~ s); 42 | 43 | auto amount = std.conv.parse!real(word); 44 | 45 | if (!word.length) 46 | { 47 | if (words.empty) 48 | { 49 | if (amount == 0) 50 | break; 51 | throw new Exception("Unit expected"); 52 | } 53 | word = words.front; 54 | words.popFront(); 55 | } 56 | 57 | Duration unit; 58 | 59 | word = word.toLower().replace("-", ""); 60 | switch (word) 61 | { 62 | case "nanoseconds": 63 | case "nanosecond": 64 | case "nsecs": 65 | case "nsec": 66 | amount /= 100; 67 | unit = 1.hnsecs; 68 | break; 69 | case "hectonanoseconds": 70 | case "hectonanosecond": 71 | case "hnsecs": 72 | case "hns": 73 | unit = 1.hnsecs; 74 | break; 75 | case "microseconds": 76 | case "microsecond": 77 | case "usecs": 78 | case "usec": 79 | case "us": 80 | case "μsecs": 81 | case "μsec": 82 | case "μs": 83 | unit = 1.usecs; 84 | break; 85 | case "milliseconds": 86 | case "millisecond": 87 | case "msecs": 88 | case "msec": 89 | case "ms": 90 | unit = 1.msecs; 91 | break; 92 | case "seconds": 93 | case "second": 94 | case "secs": 95 | case "sec": 96 | case "s": 97 | unit = 1.seconds; 98 | break; 99 | case "minutes": 100 | case "minute": 101 | case "mins": 102 | case "min": 103 | case "m": 104 | unit = 1.minutes; 105 | break; 106 | case "hours": 107 | case "hour": 108 | case "h": 109 | unit = 1.hours; 110 | break; 111 | case "days": 112 | case "day": 113 | case "d": 114 | unit = 1.days; 115 | break; 116 | case "weeks": 117 | case "week": 118 | case "w": 119 | unit = 1.weeks; 120 | break; 121 | default: 122 | throw new Exception("Unknown unit: " ~ word); 123 | } 124 | 125 | result += dur!"hnsecs"(cast(long)(unit.total!"hnsecs" * amount)); 126 | } 127 | 128 | return result; 129 | } 130 | 131 | /// 132 | debug(ae_unittest) unittest 133 | { 134 | assert(parseDuration("1 day, 1 hour and 30 minutes") == 1.days + 1.hours + 30.minutes); 135 | assert(parseDuration("0.5 hours") == 30.minutes); 136 | assert(parseDuration("0") == Duration.init); 137 | } 138 | -------------------------------------------------------------------------------- /utils/typecons.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Complements the std.typecons package. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.typecons; 15 | 16 | import std.typecons; 17 | 18 | /// If `value` is not null, return its contents. 19 | /// If `value` is null, set it to `defaultValue` and return it. 20 | /// Similar to `object.require` for associative arrays, and 21 | /// Rust's `Option::get_or_insert`. 22 | ref T require(T)(ref Nullable!T value, lazy T defaultValue) 23 | { 24 | if (value.isNull) 25 | value = defaultValue; 26 | return value.get(); 27 | } 28 | 29 | /// 30 | debug(ae_unittest) unittest 31 | { 32 | Nullable!int i; 33 | assert(i.require(3) == 3); 34 | assert(i.require(4) == 3); 35 | } 36 | 37 | deprecated alias map = std.typecons.apply; 38 | 39 | debug(ae_unittest) deprecated unittest 40 | { 41 | assert(Nullable!int( ).map!(n => n+1).isNull); 42 | assert(Nullable!int(1).map!(n => n+1).get() == 2); 43 | } 44 | 45 | /// Flatten two levels of Nullable. 46 | // Cf. https://doc.rust-lang.org/std/option/enum.Option.html#method.flatten 47 | Nullable!T flatten(T)(Nullable!(Nullable!T) value) 48 | { 49 | return value.isNull 50 | ? Nullable!T.init 51 | : value.get(); 52 | } 53 | 54 | /// 55 | debug(ae_unittest) unittest 56 | { 57 | auto i = 3.nullable.nullable; 58 | assert(i.flatten.get == 3); 59 | } 60 | -------------------------------------------------------------------------------- /utils/xml/common.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared XML code. 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | * Simon Arlott 13 | */ 14 | 15 | module ae.utils.xml.common; 16 | 17 | import ae.utils.exception; 18 | 19 | /// Exception thrown on XML parsing errors. 20 | mixin DeclareException!q{XmlParseException}; 21 | -------------------------------------------------------------------------------- /utils/xml/helpers.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Useful ae.utils.xml helpers 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.xml.helpers; 15 | 16 | import std.algorithm.iteration; 17 | import std.array; 18 | 19 | import ae.utils.xml.lite; 20 | 21 | /// Returns `true` if node `n` is a tag, and its tag name is `tag`. 22 | bool isTag(XmlNode n, string tag, XmlNodeType type = XmlNodeType.Node) 23 | { 24 | return n.type == type && n.tag == tag; 25 | } 26 | 27 | /// Like `isTag`, but if `n` does not satisfy the criteria and it has one child node, 28 | /// check it recursively instead. Returns the satisfying node or `null`. 29 | XmlNode findOnlyChild(XmlNode n, string tag, XmlNodeType type = XmlNodeType.Node) 30 | { 31 | return n.isTag(tag, type) ? n : 32 | n.children.length != 1 ? null : 33 | n.children[0].findOnlyChild(tag, type); 34 | } 35 | 36 | /// ditto 37 | XmlNode findOnlyChild(XmlNode n, XmlNodeType type) 38 | { 39 | return n.type == type ? n : 40 | n.children.length != 1 ? null : 41 | n.children[0].findOnlyChild(type); 42 | } 43 | 44 | /// Search recursively for all nodes which are tags and have the tag name `tag`. 45 | XmlNode[] findNodes(XmlNode n, string tag) 46 | { 47 | if (n.isTag(tag)) 48 | return [n]; 49 | return n.children.map!(n => findNodes(n, tag)).join; 50 | } 51 | 52 | /// Create a new node with the given properties. 53 | XmlNode newNode(XmlNodeType type, string tag, string[string] attributes = null, XmlNode[] children = null) 54 | { 55 | auto node = new XmlNode(type, tag); 56 | node.attributes = attributes; 57 | node.children = children; 58 | return node; 59 | } 60 | 61 | /// Create a tag new node with the given properties. 62 | XmlNode newNode(string tag, string[string] attributes = null, XmlNode[] children = null) 63 | { 64 | return newNode(XmlNodeType.Node, tag, attributes, children); 65 | } 66 | 67 | /// Create a text node with the given contents. 68 | XmlNode newTextNode(string text) 69 | { 70 | return newNode(XmlNodeType.Text, text); 71 | } 72 | -------------------------------------------------------------------------------- /utils/xml/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | * ae.utils.xml 3 | * 4 | * License: 5 | * This Source Code Form is subject to the terms of 6 | * the Mozilla Public License, v. 2.0. If a copy of 7 | * the MPL was not distributed with this file, You 8 | * can obtain one at http://mozilla.org/MPL/2.0/. 9 | * 10 | * Authors: 11 | * Vladimir Panteleev 12 | */ 13 | 14 | module ae.utils.xml; 15 | 16 | public import ae.utils.xmllite; 17 | public import ae.utils.xmlbuild; 18 | -------------------------------------------------------------------------------- /utils/xmllite.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Light read-only XML library 3 | * May be deprecated in the future. 4 | * See other XML modules for better implementations. 5 | * 6 | * License: 7 | * This Source Code Form is subject to the terms of 8 | * the Mozilla Public License, v. 2.0. If a copy of 9 | * the MPL was not distributed with this file, You 10 | * can obtain one at http://mozilla.org/MPL/2.0/. 11 | * 12 | * Authors: 13 | * Vladimir Panteleev 14 | * Simon Arlott 15 | */ 16 | 17 | module ae.utils.xmllite; 18 | 19 | public import ae.utils.xml.lite; 20 | public import ae.utils.xml.entities; 21 | --------------------------------------------------------------------------------