Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd8edee77f |
@@ -1,33 +0,0 @@
|
||||
Standard: Cpp11
|
||||
UseTab: ForIndentation
|
||||
TabWidth: 1
|
||||
IndentWidth: 1
|
||||
AccessModifierOffset: -1
|
||||
PointerAlignment: Left
|
||||
NamespaceIndentation: All
|
||||
ColumnLimit: 200
|
||||
BreakBeforeBraces: Allman
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
Cpp11BracedListStyle: true
|
||||
IndentCaseLabels: false
|
||||
SortIncludes: false
|
||||
ReflowComments: true
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignTrailingComments: true
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
IndentWrappedFunctionNames: false
|
||||
|
||||
#SpaceAfterTemplateKeyword: false
|
||||
@@ -1,7 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*.{h,cpp,hpp}]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
@@ -1,24 +0,0 @@
|
||||
<!---
|
||||
|
||||
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
|
||||
https://forums.rpcs3.net
|
||||
or our discord:
|
||||
https://discord.me/RPCS3
|
||||
|
||||
PLEASE READ THE GUIDELINES BEFORE OPENING AN ISSUE:
|
||||
https://github.com/RPCS3/rpcs3/blob/master/CONTRIBUTING.md
|
||||
|
||||
====================================================
|
||||
|
||||
When submitting an issue, please check the following:
|
||||
|
||||
- You have read the above.
|
||||
- You have provided the version (commit hash) of RPCS3 you are using.
|
||||
- You have provided sufficient detail for the issue to be reproduced.
|
||||
- You have provided system specs.
|
||||
- Please also provide:
|
||||
- For crashes, a backtrace.
|
||||
- For graphical issues, comparison screenshots with real hardware.
|
||||
|
||||
Remember that the GitHub Issue Tracker is not the place to ask for support or to submit Game Compatibility reports. You must use our forums for that.
|
||||
--->
|
||||
+1
-15
@@ -38,13 +38,8 @@
|
||||
/rpcs3/Debug
|
||||
/rpcs3/Release
|
||||
/llvm_build
|
||||
/Vulkan/Vulkan-build
|
||||
/Vulkan/glslang-build
|
||||
|
||||
# Themes
|
||||
!/bin/GuiConfigs/
|
||||
/bin/GuiConfigs/*.ini
|
||||
|
||||
/wxWidgets/lib
|
||||
/bin/rpcs3.ini
|
||||
/bin/rpcs3.ipdb
|
||||
/bin/rpcs3.iobj
|
||||
@@ -58,11 +53,9 @@
|
||||
rpcs3/git-version.h
|
||||
|
||||
# Visual Studio Files
|
||||
.vs/*
|
||||
*.vspx
|
||||
*.psess
|
||||
*.VC.*
|
||||
*.vcxproj.user
|
||||
|
||||
# Copyrighted files
|
||||
/bin/data/
|
||||
@@ -74,7 +67,6 @@ rpcs3/git-version.h
|
||||
!/bin/dev_hdd0/game/
|
||||
/bin/dev_hdd0/game/*
|
||||
!/bin/dev_hdd0/game/TEST12345/
|
||||
/bin/dev_hdd0/disc/*
|
||||
|
||||
# Ignore other system generated files
|
||||
bin/dev_hdd0/*.txt
|
||||
@@ -91,9 +83,3 @@ CMakeCache.txt
|
||||
# cotire
|
||||
rpcs3/cotire/*
|
||||
rpcs3/rpcs3_*_cotire.cmake
|
||||
|
||||
# Qt
|
||||
moc_*.cpp
|
||||
qrc_resources.cpp
|
||||
rpcs3_automoc.cpp
|
||||
ui_*.h
|
||||
|
||||
+15
-32
@@ -1,5 +1,9 @@
|
||||
[submodule "wxWidgets"]
|
||||
path = wxWidgets
|
||||
url = https://github.com/wxWidgets/wxWidgets
|
||||
ignore = dirty
|
||||
[submodule "rpcs3-ffmpeg"]
|
||||
path = 3rdparty/ffmpeg
|
||||
path = ffmpeg
|
||||
url = https://github.com/hrydgard/ppsspp-ffmpeg
|
||||
[submodule "asmjit"]
|
||||
path = asmjit
|
||||
@@ -7,38 +11,17 @@
|
||||
ignore = dirty
|
||||
[submodule "llvm"]
|
||||
path = llvm
|
||||
url = https://github.com/RPCS3/llvm
|
||||
branch = release_60
|
||||
url = https://github.com/llvm-mirror/llvm
|
||||
branch = release_36
|
||||
[submodule "minidx9"]
|
||||
path = minidx9
|
||||
url = https://github.com/hrydgard/minidx9.git
|
||||
[submodule "rsx_program_decompiler"]
|
||||
path = rsx_program_decompiler
|
||||
url = https://github.com/RPCS3/rsx_program_decompiler
|
||||
[submodule "GSL"]
|
||||
path = 3rdparty/GSL
|
||||
path = GSL
|
||||
url = https://github.com/Microsoft/GSL.git
|
||||
[submodule "libpng"]
|
||||
path = 3rdparty/libpng
|
||||
path = libpng
|
||||
url = https://github.com/RPCS3/libpng
|
||||
ignore = dirty
|
||||
[submodule "Vulkan/glslang"]
|
||||
path = Vulkan/glslang
|
||||
url = https://github.com/KhronosGroup/glslang.git
|
||||
[submodule "Vulkan/Vulkan-LoaderAndValidationLayers"]
|
||||
path = Vulkan/Vulkan-LoaderAndValidationLayers
|
||||
url = https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers
|
||||
[submodule "Utilities/yaml-cpp"]
|
||||
path = Utilities/yaml-cpp
|
||||
url = https://github.com/RPCS3/yaml-cpp
|
||||
[submodule "3rdparty/cereal"]
|
||||
path = 3rdparty/cereal
|
||||
url = https://github.com/USCiLab/cereal.git
|
||||
[submodule "3rdparty/zlib"]
|
||||
path = 3rdparty/zlib
|
||||
url = https://github.com/madler/zlib
|
||||
[submodule "3rdparty/hidapi"]
|
||||
path = 3rdparty/hidapi
|
||||
url = https://github.com/RPCS3/hidapi
|
||||
branch = master
|
||||
ignore = dirty
|
||||
[submodule "3rdparty/Optional"]
|
||||
path = 3rdparty/Optional
|
||||
url = https://github.com/akrzemi1/Optional.git
|
||||
[submodule "3rdparty/pugixml"]
|
||||
path = 3rdparty/pugixml
|
||||
url = https://github.com/zeux/pugixml
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"userBlacklist": ["AlexAltea", "tambry", "DHrpcs3"]
|
||||
}
|
||||
"userBlacklist": ["AlexAltea"]
|
||||
}
|
||||
+69
-95
@@ -1,124 +1,98 @@
|
||||
language: cpp
|
||||
sudo: required
|
||||
|
||||
os:
|
||||
- linux
|
||||
#- osx
|
||||
- linux
|
||||
- osx
|
||||
|
||||
# osx_image: xcode6.4
|
||||
osx_image: xcode6.4
|
||||
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
cache:
|
||||
ccache: true
|
||||
directories:
|
||||
- $HOME/hombebrew_cache
|
||||
env:
|
||||
global:
|
||||
- secure: "Vf+FY48nip9JppMnq11105NealdErSWsoUhHo63/V3V+LKfA9guenxCp93/qoSIdSGC/sJwb0yIIMGvkTT/rxDJNh6Z+BWUTb2E0WEIIQbvTJNOSUzoq7dfF1LT61XjVjByFzcbC2xjtaBowmcAYEs1jGUUuEjYVCMmD5lY8hUg="
|
||||
# Which Travis environment to run Coverity on
|
||||
- coverity_scan_run_condition='"$TRAVIS_OS_NAME" = linux -a "$CC" = gcc'
|
||||
# Test mode is for testing if it's working with Coverity. Change to true if testing, to avoid reaching the quota.
|
||||
- coverity_scan_script_test_mode=false
|
||||
|
||||
branches:
|
||||
except:
|
||||
- ppu_recompiler
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
exclude:
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
|
||||
git:
|
||||
depth: false # Unshallow clone to obtain proper GIT_VERSION
|
||||
submodules: false
|
||||
|
||||
before_install:
|
||||
# shutdown services on Travis, which may have a memory impact
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
sudo apt-get install libwxgtk3.0-dev;
|
||||
sudo apt-add-repository -y ppa:libreoffice/ppa;
|
||||
sudo apt-get update;
|
||||
sudo apt-get install libglew-dev;
|
||||
fi;
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$CXX" = "g++" ]; then
|
||||
export CXX="g++-5" CC="gcc-5" CXXFLAGS="-Wno-format-security";
|
||||
export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01';
|
||||
elif [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
export CXX="clang++-3.6" CC="clang-3.6";
|
||||
fi;
|
||||
# Add coverall for C++ so coverall.io could be triggered. Even it should be --coverage and gcov.
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
wget http://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run ;
|
||||
chmod a+x ./qt-unified-linux-x64-online.run ;
|
||||
export QT_QPA_PLATFORM=minimal ;
|
||||
travis_wait 60 ./qt-unified-linux-x64-online.run --script qt-installer-noninteractive.qs --no-force-installations --verbose ;
|
||||
fi;
|
||||
|
||||
# Install updated libglew-dev since the version provided by trusty is outdated
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
wget https://mirrors.kernel.org/ubuntu/pool/universe/g/glew/libglew-dev_2.0.0-5_amd64.deb;
|
||||
wget https://mirrors.kernel.org/ubuntu/pool/universe/g/glew/libglew2.0_2.0.0-5_amd64.deb;
|
||||
wget https://mirrors.kernel.org/ubuntu/pool/universe/v/vulkan/libvulkan1_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb;
|
||||
wget https://mirrors.kernel.org/ubuntu/pool/universe/v/vulkan/libvulkan-dev_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb;
|
||||
sudo dpkg -i libglew2.0_2.0.0-5_amd64.deb libglew-dev_2.0.0-5_amd64.deb libvulkan1_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb libvulkan-dev_1.0.61.1+dfsg1-1ubuntu1~16.04.1_amd64.deb;
|
||||
sudo pip install cpp-coveralls requests[security];
|
||||
else
|
||||
brew update;
|
||||
brew install ccache glew llvm40;
|
||||
brew update; brew update;
|
||||
brew install glew wxwidgets llvm36;
|
||||
fi;
|
||||
|
||||
before_script:
|
||||
- git submodule update --init asmjit 3rdparty/ffmpeg 3rdparty/pugixml 3rdparty/GSL 3rdparty/libpng Utilities/yaml-cpp 3rdparty/cereal 3rdparty/hidapi 3rdparty/Optional Vulkan/glslang Vulkan/Vulkan-LoaderAndValidationLayers
|
||||
- mkdir build
|
||||
- cd build
|
||||
- export CMAKE_PREFIX_PATH=~/Qt/5.10.0/gcc_64/lib/cmake
|
||||
- export CXXFLAGS="$CXXFLAGS -DTRAVIS=true";
|
||||
- if [ "$TRAVIS_PULL_REQUEST" = false ]; then
|
||||
export CXXFLAGS="$CXXFLAGS -DBRANCH=$TRAVIS_REPO_SLUG/$TRAVIS_BRANCH";
|
||||
else
|
||||
export CXXFLAGS="$CXXFLAGS -DBRANCH=$TRAVIS_REPO_SLUG/$TRAVIS_BRANCH/#$TRAVIS_PULL_REQUEST";
|
||||
fi;
|
||||
- cmake .. -DCMAKE_INSTALL_PREFIX=/usr -G Ninja;
|
||||
- ninja
|
||||
- # AppImage generation
|
||||
- if [ -n "$UPLOAD_URL" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ "$CC" = "clang" ] && [ "$TRAVIS_PULL_REQUEST" = false ]; then
|
||||
export LD_LIBRARY_PATH=~/Qt/5.10.0/gcc_64/lib;
|
||||
DESTDIR=appdir ninja install ; find appdir/ ;
|
||||
find ../bin ;
|
||||
wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" ;
|
||||
chmod a+x linuxdeployqt*.AppImage ;
|
||||
export PATH=~/Qt/5.10.0/gcc_64/bin/:${PATH} ;
|
||||
./linuxdeployqt*.AppImage --appimage-extract ;
|
||||
./linuxdeployqt*.AppImage ./appdir/usr/share/applications/*.desktop -bundle-non-qt-libs ;
|
||||
mkdir ./appdir/usr/plugins/xcbglintegrations/ ;
|
||||
mkdir ./appdir/usr/plugins/imageformats/ ;
|
||||
cp ~/Qt/5.10.0/gcc_64/lib/libQt5Svg.so.5 ./appdir/usr/lib/ ;
|
||||
cp ~/Qt/5.10.0/gcc_64/plugins/xcbglintegrations/* ./appdir/usr/plugins/xcbglintegrations/ ;
|
||||
cp ~/Qt/5.10.0/gcc_64/plugins/imageformats/* ./appdir/usr/plugins/imageformats/ ;
|
||||
cp ~/Qt/5.10.0/gcc_64/plugins/platforms/* ./appdir/usr/plugins/platforms/ ;
|
||||
rm ./appdir/usr/lib/libfreetype.so.6 ;
|
||||
export PATH=${TRAVIS_BUILD_DIR}/build/squashfs-root/usr/bin/:${PATH} ;
|
||||
./squashfs-root/usr/bin/appimagetool ${TRAVIS_BUILD_DIR}/build/appdir ;
|
||||
find ./appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq ;
|
||||
curl ${UPLOAD_URL}${TRAVIS_COMMIT:0:7}-${TRAVIS_BUILD_NUMBER}_linux64 --upload-file ./RPCS3*.AppImage;
|
||||
fi;
|
||||
- git submodule update --init asmjit ffmpeg rsx_program_decompiler GSL libpng
|
||||
- mkdir build
|
||||
- cd build
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then cmake ..; else cmake .. -DLLVM_DIR=/usr/local/opt/llvm36/lib/llvm-3.6/share/llvm/cmake; fi
|
||||
|
||||
script:
|
||||
# Add a command to show all the variables. May be useful for debugging Travis.
|
||||
#- echo "--Shell Export Lists START--" ; export -p; echo "--Shell Export Lists STOP--";
|
||||
# And to ensure the versions of toolchain
|
||||
- echo "--CXX version?"; "$CXX" --version; echo "--CXX version confirmed";
|
||||
# Add a command to show all the variables. May be useful for debugging Travis.
|
||||
# - echo "--Shell Export Lists START--" ; export -p; echo "--Shell Export Lists STOP--";
|
||||
# And to ensure the versions of toolchain
|
||||
- echo "--CXX version?"; "$CXX" --version; echo "--CXX version confirmed";
|
||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then make -j 4; fi
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-4.0
|
||||
- sourceline: 'ppa:jonathonf/binutils' # We need to update binutils to a newer version to link against the ffmpeg libs on.
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.6
|
||||
- kubuntu-backports
|
||||
packages:
|
||||
- binutils
|
||||
- cmake
|
||||
- ninja-build
|
||||
- libasound2-dev
|
||||
- libopenal-dev
|
||||
- freeglut3-dev
|
||||
#- libglew-dev
|
||||
#- libvulkan1
|
||||
#- libvulkan-dev
|
||||
- libc6-dev
|
||||
- llvm-4.0
|
||||
- llvm-4.0-dev
|
||||
# Clang 5.0 is now bundled in travis, so we no longer need the ppa version.
|
||||
#- clang-4.0
|
||||
- libedit-dev
|
||||
- g++-5
|
||||
- gcc-5
|
||||
- libstdc++-5-dev
|
||||
- lib32stdc++6
|
||||
- zlib1g-dev
|
||||
# We need to install qt 5.10.0 manually because the version trusty provides is too old.
|
||||
#- qtbase5-dev
|
||||
- libudev-dev
|
||||
- libevdev-dev
|
||||
- libpulse-dev
|
||||
- cmake
|
||||
- libopenal-dev
|
||||
- freeglut3-dev
|
||||
# - libglew-dev apt version is too old
|
||||
- libc6-dev
|
||||
- llvm-3.6
|
||||
- llvm-3.6-dev
|
||||
- libedit-dev
|
||||
- g++-5
|
||||
- gcc-5
|
||||
- clang-3.6
|
||||
- libstdc++-4.8-dev
|
||||
- lib32stdc++6
|
||||
- zlib1g-dev
|
||||
coverity_scan:
|
||||
project:
|
||||
name: $TRAVIS_REPO_SLUG
|
||||
description: "PS3 emulator/debugger"
|
||||
notification_email: raul.tambre@gmail.com
|
||||
build_command: "make -j 4"
|
||||
branch_pattern: coverity_scan
|
||||
|
||||
after_success:
|
||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "$TRAVIS_OS_NAME" = linux ]; then coveralls --extension .c --extension .cpp --extension .h; fi
|
||||
|
||||
Vendored
-1
Submodule 3rdparty/Optional deleted from f27e79084a
Vendored
-1295
File diff suppressed because it is too large
Load Diff
Vendored
-263
@@ -1,263 +0,0 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* File: audiodefs.h
|
||||
* Content: Basic constants and data types for audio work.
|
||||
*
|
||||
* Remarks: This header file defines all of the audio format constants and
|
||||
* structures required for XAudio2 and XACT work. Providing these
|
||||
* in a single location avoids certain dependency problems in the
|
||||
* legacy audio headers (mmreg.h, mmsystem.h, ksmedia.h).
|
||||
*
|
||||
* NOTE: Including the legacy headers after this one may cause a
|
||||
* compilation error, because they define some of the same types
|
||||
* defined here without preprocessor guards to avoid multiple
|
||||
* definitions. If a source file needs one of the old headers,
|
||||
* it must include it before including audiodefs.h.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef __AUDIODEFS_INCLUDED__
|
||||
#define __AUDIODEFS_INCLUDED__
|
||||
|
||||
#include <windef.h> // For WORD, DWORD, etc.
|
||||
|
||||
#pragma pack(push, 1) // Pack structures to 1-byte boundaries
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* WAVEFORMATEX: Base structure for many audio formats. Format-specific
|
||||
* extensions can be defined for particular formats by using a non-zero
|
||||
* cbSize value and adding extra fields to the end of this structure.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef _WAVEFORMATEX_
|
||||
|
||||
#define _WAVEFORMATEX_
|
||||
typedef struct tWAVEFORMATEX
|
||||
{
|
||||
WORD wFormatTag; // Integer identifier of the format
|
||||
WORD nChannels; // Number of audio channels
|
||||
DWORD nSamplesPerSec; // Audio sample rate
|
||||
DWORD nAvgBytesPerSec; // Bytes per second (possibly approximate)
|
||||
WORD nBlockAlign; // Size in bytes of a sample block (all channels)
|
||||
WORD wBitsPerSample; // Size in bits of a single per-channel sample
|
||||
WORD cbSize; // Bytes of extra data appended to this struct
|
||||
} WAVEFORMATEX;
|
||||
|
||||
#endif
|
||||
|
||||
// Defining pointer types outside of the #if block to make sure they are
|
||||
// defined even if mmreg.h or mmsystem.h is #included before this file
|
||||
|
||||
typedef WAVEFORMATEX *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX;
|
||||
typedef const WAVEFORMATEX *PCWAVEFORMATEX, *LPCWAVEFORMATEX;
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* WAVEFORMATEXTENSIBLE: Extended version of WAVEFORMATEX that should be
|
||||
* used as a basis for all new audio formats. The format tag is replaced
|
||||
* with a GUID, allowing new formats to be defined without registering a
|
||||
* format tag with Microsoft. There are also new fields that can be used
|
||||
* to specify the spatial positions for each channel and the bit packing
|
||||
* used for wide samples (e.g. 24-bit PCM samples in 32-bit containers).
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
|
||||
#define _WAVEFORMATEXTENSIBLE_
|
||||
typedef struct
|
||||
{
|
||||
WAVEFORMATEX Format; // Base WAVEFORMATEX data
|
||||
union
|
||||
{
|
||||
WORD wValidBitsPerSample; // Valid bits in each sample container
|
||||
WORD wSamplesPerBlock; // Samples per block of audio data; valid
|
||||
// if wBitsPerSample=0 (but rarely used).
|
||||
WORD wReserved; // Zero if neither case above applies.
|
||||
} Samples;
|
||||
DWORD dwChannelMask; // Positions of the audio channels
|
||||
GUID SubFormat; // Format identifier GUID
|
||||
} WAVEFORMATEXTENSIBLE;
|
||||
|
||||
#endif
|
||||
|
||||
typedef WAVEFORMATEXTENSIBLE *PWAVEFORMATEXTENSIBLE, *LPWAVEFORMATEXTENSIBLE;
|
||||
typedef const WAVEFORMATEXTENSIBLE *PCWAVEFORMATEXTENSIBLE, *LPCWAVEFORMATEXTENSIBLE;
|
||||
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* Define the most common wave format tags used in WAVEFORMATEX formats.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef WAVE_FORMAT_PCM // Pulse Code Modulation
|
||||
|
||||
// If WAVE_FORMAT_PCM is not defined, we need to define some legacy types
|
||||
// for compatibility with the Windows mmreg.h / mmsystem.h header files.
|
||||
|
||||
// Old general format structure (information common to all formats)
|
||||
typedef struct waveformat_tag
|
||||
{
|
||||
WORD wFormatTag;
|
||||
WORD nChannels;
|
||||
DWORD nSamplesPerSec;
|
||||
DWORD nAvgBytesPerSec;
|
||||
WORD nBlockAlign;
|
||||
} WAVEFORMAT, *PWAVEFORMAT, NEAR *NPWAVEFORMAT, FAR *LPWAVEFORMAT;
|
||||
|
||||
// Specific format structure for PCM data
|
||||
typedef struct pcmwaveformat_tag
|
||||
{
|
||||
WAVEFORMAT wf;
|
||||
WORD wBitsPerSample;
|
||||
} PCMWAVEFORMAT, *PPCMWAVEFORMAT, NEAR *NPPCMWAVEFORMAT, FAR *LPPCMWAVEFORMAT;
|
||||
|
||||
#define WAVE_FORMAT_PCM 0x0001
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_ADPCM // Microsoft Adaptive Differental PCM
|
||||
|
||||
// Replicate the Microsoft ADPCM type definitions from mmreg.h.
|
||||
|
||||
typedef struct adpcmcoef_tag
|
||||
{
|
||||
short iCoef1;
|
||||
short iCoef2;
|
||||
} ADPCMCOEFSET;
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4200) // Disable zero-sized array warnings
|
||||
|
||||
typedef struct adpcmwaveformat_tag {
|
||||
WAVEFORMATEX wfx;
|
||||
WORD wSamplesPerBlock;
|
||||
WORD wNumCoef;
|
||||
ADPCMCOEFSET aCoef[]; // Always 7 coefficient pairs for MS ADPCM
|
||||
} ADPCMWAVEFORMAT;
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
#define WAVE_FORMAT_ADPCM 0x0002
|
||||
|
||||
#endif
|
||||
|
||||
// Other frequently used format tags
|
||||
|
||||
#ifndef WAVE_FORMAT_UNKNOWN
|
||||
#define WAVE_FORMAT_UNKNOWN 0x0000 // Unknown or invalid format tag
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_MPEGLAYER3
|
||||
#define WAVE_FORMAT_MPEGLAYER3 0x0055 // ISO/MPEG Layer3
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_DOLBY_AC3_SPDIF
|
||||
#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 // Dolby Audio Codec 3 over S/PDIF
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_WMAUDIO2
|
||||
#define WAVE_FORMAT_WMAUDIO2 0x0161 // Windows Media Audio
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_WMAUDIO3
|
||||
#define WAVE_FORMAT_WMAUDIO3 0x0162 // Windows Media Audio Pro
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_WMASPDIF
|
||||
#define WAVE_FORMAT_WMASPDIF 0x0164 // Windows Media Audio over S/PDIF
|
||||
#endif
|
||||
|
||||
#ifndef WAVE_FORMAT_EXTENSIBLE
|
||||
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE // All WAVEFORMATEXTENSIBLE formats
|
||||
#endif
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* Define the most common wave format GUIDs used in WAVEFORMATEXTENSIBLE
|
||||
* formats. Note that including the Windows ksmedia.h header after this
|
||||
* one will cause build problems; this cannot be avoided, since ksmedia.h
|
||||
* defines these macros without preprocessor guards.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifdef __cplusplus // uuid() and __uuidof() are only available in C++
|
||||
|
||||
#ifndef KSDATAFORMAT_SUBTYPE_PCM
|
||||
struct __declspec(uuid("00000001-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_PCM_STRUCT;
|
||||
#define KSDATAFORMAT_SUBTYPE_PCM __uuidof(KSDATAFORMAT_SUBTYPE_PCM_STRUCT)
|
||||
#endif
|
||||
|
||||
#ifndef KSDATAFORMAT_SUBTYPE_ADPCM
|
||||
struct __declspec(uuid("00000002-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT;
|
||||
#define KSDATAFORMAT_SUBTYPE_ADPCM __uuidof(KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT)
|
||||
#endif
|
||||
|
||||
#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
|
||||
struct __declspec(uuid("00000003-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT;
|
||||
#define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT __uuidof(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* Speaker positions used in the WAVEFORMATEXTENSIBLE dwChannelMask field.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef SPEAKER_FRONT_LEFT
|
||||
#define SPEAKER_FRONT_LEFT 0x00000001
|
||||
#define SPEAKER_FRONT_RIGHT 0x00000002
|
||||
#define SPEAKER_FRONT_CENTER 0x00000004
|
||||
#define SPEAKER_LOW_FREQUENCY 0x00000008
|
||||
#define SPEAKER_BACK_LEFT 0x00000010
|
||||
#define SPEAKER_BACK_RIGHT 0x00000020
|
||||
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040
|
||||
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080
|
||||
#define SPEAKER_BACK_CENTER 0x00000100
|
||||
#define SPEAKER_SIDE_LEFT 0x00000200
|
||||
#define SPEAKER_SIDE_RIGHT 0x00000400
|
||||
#define SPEAKER_TOP_CENTER 0x00000800
|
||||
#define SPEAKER_TOP_FRONT_LEFT 0x00001000
|
||||
#define SPEAKER_TOP_FRONT_CENTER 0x00002000
|
||||
#define SPEAKER_TOP_FRONT_RIGHT 0x00004000
|
||||
#define SPEAKER_TOP_BACK_LEFT 0x00008000
|
||||
#define SPEAKER_TOP_BACK_CENTER 0x00010000
|
||||
#define SPEAKER_TOP_BACK_RIGHT 0x00020000
|
||||
#define SPEAKER_RESERVED 0x7FFC0000
|
||||
#define SPEAKER_ALL 0x80000000
|
||||
#define _SPEAKER_POSITIONS_
|
||||
#endif
|
||||
|
||||
#ifndef SPEAKER_STEREO
|
||||
#define SPEAKER_MONO (SPEAKER_FRONT_CENTER)
|
||||
#define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
|
||||
#define SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY)
|
||||
#define SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER)
|
||||
#define SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
|
||||
#define SPEAKER_4POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
|
||||
#define SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
|
||||
#define SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER)
|
||||
#define SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
|
||||
#define SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
|
||||
#endif
|
||||
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif // #ifndef __AUDIODEFS_INCLUDED__
|
||||
Vendored
-65
@@ -1,65 +0,0 @@
|
||||
// comdecl.h: Macros to facilitate COM interface and GUID declarations.
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
#ifndef _COMDECL_H_
|
||||
#define _COMDECL_H_
|
||||
|
||||
#ifndef _XBOX
|
||||
#include <basetyps.h> // For standard COM interface macros
|
||||
#else
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4061)
|
||||
#include <xtl.h> // Required by xobjbase.h
|
||||
#include <xobjbase.h> // Special definitions for Xbox build
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
// The DEFINE_CLSID() and DEFINE_IID() macros defined below allow COM GUIDs to
|
||||
// be declared and defined in such a way that clients can obtain the GUIDs using
|
||||
// either the __uuidof() extension or the old-style CLSID_Foo / IID_IFoo names.
|
||||
// If using the latter approach, the client can also choose whether to get the
|
||||
// GUID definitions by defining the INITGUID preprocessor constant or by linking
|
||||
// to a GUID library. This works in either C or C++.
|
||||
|
||||
#ifndef _MSC_VER
|
||||
|
||||
#define DEFINE_UUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) __CRT_UUID_DECL(name, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
#define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) class className; DEFINE_UUID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8)
|
||||
#define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) struct interfaceName; DEFINE_UUID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8)
|
||||
|
||||
#elif __cplusplus
|
||||
|
||||
#define DECLSPEC_UUID_WRAPPER(x) __declspec(uuid(#x))
|
||||
#ifdef INITGUID
|
||||
|
||||
#define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \
|
||||
EXTERN_C const GUID DECLSPEC_SELECTANY CLSID_##className = __uuidof(className)
|
||||
|
||||
#define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \
|
||||
EXTERN_C const GUID DECLSPEC_SELECTANY IID_##interfaceName = __uuidof(interfaceName)
|
||||
|
||||
#else // INITGUID
|
||||
|
||||
#define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \
|
||||
EXTERN_C const GUID CLSID_##className
|
||||
|
||||
#define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \
|
||||
EXTERN_C const GUID IID_##interfaceName
|
||||
|
||||
#endif // INITGUID
|
||||
|
||||
#else // __cplusplus
|
||||
|
||||
#define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
|
||||
#define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
||||
DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8)
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // #ifndef _COMDECL_H_
|
||||
Vendored
-18
@@ -1,18 +0,0 @@
|
||||
/*==========================================================================;
|
||||
*
|
||||
*
|
||||
* File: dxsdkver.h
|
||||
* Content: DirectX SDK Version Include File
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef _DXSDKVER_H_
|
||||
#define _DXSDKVER_H_
|
||||
|
||||
#define _DXSDK_PRODUCT_MAJOR 9
|
||||
#define _DXSDK_PRODUCT_MINOR 29
|
||||
#define _DXSDK_BUILD_MAJOR 1962
|
||||
#define _DXSDK_BUILD_MINOR 0
|
||||
|
||||
#endif // _DXSDKVER_H_
|
||||
|
||||
Vendored
-718
@@ -1,718 +0,0 @@
|
||||
/***************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* File: xma2defs.h
|
||||
* Content: Constants, data types and functions for XMA2 compressed audio.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef __XMA2DEFS_INCLUDED__
|
||||
#define __XMA2DEFS_INCLUDED__
|
||||
|
||||
#include <sal.h> // Markers for documenting API semantics
|
||||
#include <winerror.h> // For S_OK, E_FAIL
|
||||
#include <audiodefs.h> // Basic data types and constants for audio work
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Overview
|
||||
***************************************************************************/
|
||||
|
||||
// A typical XMA2 file contains these RIFF chunks:
|
||||
//
|
||||
// 'fmt' or 'XMA2' chunk (or both): A description of the XMA data's structure
|
||||
// and characteristics (length, channels, sample rate, loops, block size, etc).
|
||||
//
|
||||
// 'seek' chunk: A seek table to help navigate the XMA data.
|
||||
//
|
||||
// 'data' chunk: The encoded XMA2 data.
|
||||
//
|
||||
// The encoded XMA2 data is structured as a set of BLOCKS, which contain PACKETS,
|
||||
// which contain FRAMES, which contain SUBFRAMES (roughly speaking). The frames
|
||||
// in a file may also be divided into several subsets, called STREAMS.
|
||||
//
|
||||
// FRAME: A variable-sized segment of XMA data that decodes to exactly 512 mono
|
||||
// or stereo PCM samples. This is the smallest unit of XMA data that can
|
||||
// be decoded in isolation. Frames are an arbitrary number of bits in
|
||||
// length, and need not be byte-aligned. See "XMA frame structure" below.
|
||||
//
|
||||
// SUBFRAME: A region of bits in an XMA frame that decodes to 128 mono or stereo
|
||||
// samples. The XMA decoder cannot decode a subframe in isolation; it needs
|
||||
// a whole frame to work with. However, it can begin emitting the frame's
|
||||
// decoded samples at any one of the four subframe boundaries. Subframes
|
||||
// can be addressed for seeking and looping purposes.
|
||||
//
|
||||
// PACKET: A 2Kb region containing a 32-bit header and some XMA frames. Frames
|
||||
// can (and usually do) span packets. A packet's header includes the offset
|
||||
// in bits of the first frame that begins within that packet. All of the
|
||||
// frames that begin in a given packet belong to the same "stream" (see the
|
||||
// Multichannel Audio section below).
|
||||
//
|
||||
// STREAM: A set of packets within an XMA file that all contain data for the
|
||||
// same mono or stereo component of a PCM file with more than two channels.
|
||||
// The packets comprising a given stream may be interleaved with each other
|
||||
// more or less arbitrarily; see Multichannel Audio.
|
||||
//
|
||||
// BLOCK: An array of XMA packets; or, to break it down differently, a series of
|
||||
// consecutive XMA frames, padded at the end with reserved data. A block
|
||||
// must contain at least one 2Kb packet per stream, and it can hold up to
|
||||
// 4095 packets (8190Kb), but its size is typically in the 32Kb-128Kb range.
|
||||
// (The size chosen involves a trade-off between memory use and efficiency
|
||||
// of reading from permanent storage.)
|
||||
//
|
||||
// XMA frames do not span blocks, so a block is guaranteed to begin with a
|
||||
// set of complete frames, one per stream. Also, a block in a multi-stream
|
||||
// XMA2 file always contains the same number of samples for each stream;
|
||||
// see Multichannel Audio.
|
||||
//
|
||||
// The 'data' chunk in an XMA2 file is an array of XMA2WAVEFORMAT.BlockCount XMA
|
||||
// blocks, all the same size (as specified in XMA2WAVEFORMAT.BlockSizeInBytes)
|
||||
// except for the last one, which may be shorter.
|
||||
|
||||
|
||||
// MULTICHANNEL AUDIO: the XMA decoder can only decode raw XMA data into either
|
||||
// mono or stereo PCM data. In order to encode a 6-channel file (say), the file
|
||||
// must be deinterleaved into 3 stereo streams that are encoded independently,
|
||||
// producing 3 encoded XMA data streams. Then the packets in these 3 streams
|
||||
// are interleaved to produce a single XMA2 file, and some information is added
|
||||
// to the file so that the original 6-channel audio can be reconstructed at
|
||||
// decode time. This works using the concept of an XMA stream (see above).
|
||||
//
|
||||
// The frames for all the streams in an XMA file are interleaved in an arbitrary
|
||||
// order. To locate a frame that belongs to a given stream in a given XMA block,
|
||||
// you must examine the first few packets in the block. Here (and only here) the
|
||||
// packets are guaranteed to be presented in stream order, so that all frames
|
||||
// beginning in packet 0 belong to stream 0 (the first stereo pair), etc.
|
||||
//
|
||||
// (This means that when decoding multi-stream XMA files, only entire XMA blocks
|
||||
// should be submitted to the decoder; otherwise it cannot know which frames
|
||||
// belong to which stream.)
|
||||
//
|
||||
// Once you have one frame that belongs to a given stream, you can find the next
|
||||
// one by looking at the frame's 'NextFrameOffsetBits' value (which is stored in
|
||||
// its first 15 bits; see XMAFRAME below). The GetXmaFrameBitPosition function
|
||||
// uses this technique.
|
||||
|
||||
|
||||
// SEEKING IN XMA2 FILES: Here is some pseudocode to find the byte position and
|
||||
// subframe in an XMA2 file which will contain sample S when decoded.
|
||||
//
|
||||
// 1. Traverse the seek table to find the XMA2 block containing sample S. The
|
||||
// seek table is an array of big-endian DWORDs, one per block in the file.
|
||||
// The Nth DWORD is the total number of PCM samples that would be obtained
|
||||
// by decoding the entire XMA file up to the end of block N. Hence, the
|
||||
// block we want is the first one whose seek table entry is greater than S.
|
||||
// (See the GetXmaBlockContainingSample helper function.)
|
||||
//
|
||||
// 2. Calculate which frame F within the block found above contains sample S.
|
||||
// Since each frame decodes to 512 samples, this is straightforward. The
|
||||
// first frame in the block produces samples X to X + 512, where X is the
|
||||
// seek table entry for the prior block. So F is (S - X) / 512.
|
||||
//
|
||||
// 3. Find the bit offset within the block where frame F starts. Since frames
|
||||
// are variable-sized, this can only be done by traversing all the frames in
|
||||
// the block until we reach frame F. (See GetXmaFrameBitPosition.)
|
||||
//
|
||||
// 4. Frame F has four 128-sample subframes. To find the subframe containing S,
|
||||
// we can use the formula (S % 512) / 128.
|
||||
//
|
||||
// In the case of multi-stream XMA files, sample S is a multichannel sample with
|
||||
// parts coming from several frames, one per stream. To find all these frames,
|
||||
// steps 2-4 need to be repeated for each stream N, using the knowledge that the
|
||||
// first packets in a block are presented in stream order. The frame traversal
|
||||
// in step 3 must be started at the first frame in the Nth packet of the block,
|
||||
// which will be the first frame for stream N. (And the packet header will tell
|
||||
// you the first frame's start position within the packet.)
|
||||
//
|
||||
// Step 1 can be performed using the GetXmaBlockContainingSample function below,
|
||||
// and steps 2-4 by calling GetXmaDecodePositionForSample once for each stream.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* XMA constants
|
||||
***************************************************************************/
|
||||
|
||||
// Size of the PCM samples produced by the XMA decoder
|
||||
#define XMA_OUTPUT_SAMPLE_BYTES 2u
|
||||
#define XMA_OUTPUT_SAMPLE_BITS (XMA_OUTPUT_SAMPLE_BYTES * 8u)
|
||||
|
||||
// Size of an XMA packet
|
||||
#define XMA_BYTES_PER_PACKET 2048u
|
||||
#define XMA_BITS_PER_PACKET (XMA_BYTES_PER_PACKET * 8u)
|
||||
|
||||
// Size of an XMA packet header
|
||||
#define XMA_PACKET_HEADER_BYTES 4u
|
||||
#define XMA_PACKET_HEADER_BITS (XMA_PACKET_HEADER_BYTES * 8u)
|
||||
|
||||
// Sample blocks in a decoded XMA frame
|
||||
#define XMA_SAMPLES_PER_FRAME 512u
|
||||
|
||||
// Sample blocks in a decoded XMA subframe
|
||||
#define XMA_SAMPLES_PER_SUBFRAME 128u
|
||||
|
||||
// Maximum encoded data that can be submitted to the XMA decoder at a time
|
||||
#define XMA_READBUFFER_MAX_PACKETS 4095u
|
||||
#define XMA_READBUFFER_MAX_BYTES (XMA_READBUFFER_MAX_PACKETS * XMA_BYTES_PER_PACKET)
|
||||
|
||||
// Maximum size allowed for the XMA decoder's output buffers
|
||||
#define XMA_WRITEBUFFER_MAX_BYTES (31u * 256u)
|
||||
|
||||
// Required byte alignment of the XMA decoder's output buffers
|
||||
#define XMA_WRITEBUFFER_BYTE_ALIGNMENT 256u
|
||||
|
||||
// Decode chunk sizes for the XMA_PLAYBACK_INIT.subframesToDecode field
|
||||
#define XMA_MIN_SUBFRAMES_TO_DECODE 1u
|
||||
#define XMA_MAX_SUBFRAMES_TO_DECODE 8u
|
||||
#define XMA_OPTIMAL_SUBFRAMES_TO_DECODE 4u
|
||||
|
||||
// LoopCount<255 means finite repetitions; LoopCount=255 means infinite looping
|
||||
#define XMA_MAX_LOOPCOUNT 254u
|
||||
#define XMA_INFINITE_LOOP 255u
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* XMA format structures
|
||||
***************************************************************************/
|
||||
|
||||
// The currently recommended way to express format information for XMA2 files
|
||||
// is the XMA2WAVEFORMATEX structure. This structure is fully compliant with
|
||||
// the WAVEFORMATEX standard and contains all the information needed to parse
|
||||
// and manage XMA2 files in a compact way.
|
||||
|
||||
#define WAVE_FORMAT_XMA2 0x166
|
||||
|
||||
typedef struct XMA2WAVEFORMATEX
|
||||
{
|
||||
WAVEFORMATEX wfx;
|
||||
// Meaning of the WAVEFORMATEX fields here:
|
||||
// wFormatTag; // Audio format type; always WAVE_FORMAT_XMA2
|
||||
// nChannels; // Channel count of the decoded audio
|
||||
// nSamplesPerSec; // Sample rate of the decoded audio
|
||||
// nAvgBytesPerSec; // Used internally by the XMA encoder
|
||||
// nBlockAlign; // Decoded sample size; channels * wBitsPerSample / 8
|
||||
// wBitsPerSample; // Bits per decoded mono sample; always 16 for XMA
|
||||
// cbSize; // Size in bytes of the rest of this structure (34)
|
||||
|
||||
WORD NumStreams; // Number of audio streams (1 or 2 channels each)
|
||||
DWORD ChannelMask; // Spatial positions of the channels in this file,
|
||||
// stored as SPEAKER_xxx values (see audiodefs.h)
|
||||
DWORD SamplesEncoded; // Total number of PCM samples the file decodes to
|
||||
DWORD BytesPerBlock; // XMA block size (but the last one may be shorter)
|
||||
DWORD PlayBegin; // First valid sample in the decoded audio
|
||||
DWORD PlayLength; // Length of the valid part of the decoded audio
|
||||
DWORD LoopBegin; // Beginning of the loop region in decoded sample terms
|
||||
DWORD LoopLength; // Length of the loop region in decoded sample terms
|
||||
BYTE LoopCount; // Number of loop repetitions; 255 = infinite
|
||||
BYTE EncoderVersion; // Version of XMA encoder that generated the file
|
||||
WORD BlockCount; // XMA blocks in file (and entries in its seek table)
|
||||
} XMA2WAVEFORMATEX, *PXMA2WAVEFORMATEX;
|
||||
|
||||
|
||||
// The legacy XMA format structures are described here for reference, but they
|
||||
// should not be used in new content. XMAWAVEFORMAT was the structure used in
|
||||
// XMA version 1 files. XMA2WAVEFORMAT was used in early XMA2 files; it is not
|
||||
// placed in the usual 'fmt' RIFF chunk but in its own 'XMA2' chunk.
|
||||
|
||||
#ifndef WAVE_FORMAT_XMA
|
||||
#define WAVE_FORMAT_XMA 0x0165
|
||||
|
||||
// Values used in the ChannelMask fields below. Similar to the SPEAKER_xxx
|
||||
// values defined in audiodefs.h, but modified to fit in a single byte.
|
||||
#ifndef XMA_SPEAKER_LEFT
|
||||
#define XMA_SPEAKER_LEFT 0x01
|
||||
#define XMA_SPEAKER_RIGHT 0x02
|
||||
#define XMA_SPEAKER_CENTER 0x04
|
||||
#define XMA_SPEAKER_LFE 0x08
|
||||
#define XMA_SPEAKER_LEFT_SURROUND 0x10
|
||||
#define XMA_SPEAKER_RIGHT_SURROUND 0x20
|
||||
#define XMA_SPEAKER_LEFT_BACK 0x40
|
||||
#define XMA_SPEAKER_RIGHT_BACK 0x80
|
||||
#endif
|
||||
|
||||
|
||||
// Used in XMAWAVEFORMAT for per-stream data
|
||||
typedef struct XMASTREAMFORMAT
|
||||
{
|
||||
DWORD PsuedoBytesPerSec; // Used by the XMA encoder (typo preserved for legacy reasons)
|
||||
DWORD SampleRate; // The stream's decoded sample rate (in XMA2 files,
|
||||
// this is the same for all streams in the file).
|
||||
DWORD LoopStart; // Bit offset of the frame containing the loop start
|
||||
// point, relative to the beginning of the stream.
|
||||
DWORD LoopEnd; // Bit offset of the frame containing the loop end.
|
||||
BYTE SubframeData; // Two 4-bit numbers specifying the exact location of
|
||||
// the loop points within the frames that contain them.
|
||||
// SubframeEnd: Subframe of the loop end frame where
|
||||
// the loop ends. Ranges from 0 to 3.
|
||||
// SubframeSkip: Subframes to skip in the start frame to
|
||||
// reach the loop. Ranges from 0 to 4.
|
||||
BYTE Channels; // Number of channels in the stream (1 or 2)
|
||||
WORD ChannelMask; // Spatial positions of the channels in the stream
|
||||
} XMASTREAMFORMAT;
|
||||
|
||||
// Legacy XMA1 format structure
|
||||
typedef struct XMAWAVEFORMAT
|
||||
{
|
||||
WORD FormatTag; // Audio format type (always WAVE_FORMAT_XMA)
|
||||
WORD BitsPerSample; // Bit depth (currently required to be 16)
|
||||
WORD EncodeOptions; // Options for XMA encoder/decoder
|
||||
WORD LargestSkip; // Largest skip used in interleaving streams
|
||||
WORD NumStreams; // Number of interleaved audio streams
|
||||
BYTE LoopCount; // Number of loop repetitions; 255 = infinite
|
||||
BYTE Version; // XMA encoder version that generated the file.
|
||||
// Always 3 or higher for XMA2 files.
|
||||
XMASTREAMFORMAT XmaStreams[1]; // Per-stream format information; the actual
|
||||
// array length is in the NumStreams field.
|
||||
} XMAWAVEFORMAT;
|
||||
|
||||
|
||||
// Used in XMA2WAVEFORMAT for per-stream data
|
||||
typedef struct XMA2STREAMFORMAT
|
||||
{
|
||||
BYTE Channels; // Number of channels in the stream (1 or 2)
|
||||
BYTE RESERVED; // Reserved for future use
|
||||
WORD ChannelMask; // Spatial positions of the channels in the stream
|
||||
} XMA2STREAMFORMAT;
|
||||
|
||||
// Legacy XMA2 format structure (big-endian byte ordering)
|
||||
typedef struct XMA2WAVEFORMAT
|
||||
{
|
||||
BYTE Version; // XMA encoder version that generated the file.
|
||||
// Always 3 or higher for XMA2 files.
|
||||
BYTE NumStreams; // Number of interleaved audio streams
|
||||
BYTE RESERVED; // Reserved for future use
|
||||
BYTE LoopCount; // Number of loop repetitions; 255 = infinite
|
||||
DWORD LoopBegin; // Loop begin point, in samples
|
||||
DWORD LoopEnd; // Loop end point, in samples
|
||||
DWORD SampleRate; // The file's decoded sample rate
|
||||
DWORD EncodeOptions; // Options for the XMA encoder/decoder
|
||||
DWORD PsuedoBytesPerSec; // Used internally by the XMA encoder
|
||||
DWORD BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except
|
||||
// possibly the last one). Always a multiple of
|
||||
// 2Kb, since XMA blocks are arrays of 2Kb packets.
|
||||
DWORD SamplesEncoded; // Total number of PCM samples encoded in this file
|
||||
DWORD SamplesInSource; // Actual number of PCM samples in the source
|
||||
// material used to generate this file
|
||||
DWORD BlockCount; // Number of XMA blocks in this file (and hence
|
||||
// also the number of entries in its seek table)
|
||||
XMA2STREAMFORMAT Streams[1]; // Per-stream format information; the actual
|
||||
// array length is in the NumStreams field.
|
||||
} XMA2WAVEFORMAT;
|
||||
|
||||
#endif // #ifndef WAVE_FORMAT_XMA
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* XMA packet structure (in big-endian form)
|
||||
***************************************************************************/
|
||||
|
||||
typedef struct XMA2PACKET
|
||||
{
|
||||
int FrameCount : 6; // Number of XMA frames that begin in this packet
|
||||
int FrameOffsetInBits : 15; // Bit of XmaData where the first complete frame begins
|
||||
int PacketMetaData : 3; // Metadata stored in the packet (always 1 for XMA2)
|
||||
int PacketSkipCount : 8; // How many packets belonging to other streams must be
|
||||
// skipped to find the next packet belonging to this one
|
||||
BYTE XmaData[XMA_BYTES_PER_PACKET - sizeof(DWORD)]; // XMA encoded data
|
||||
} XMA2PACKET;
|
||||
|
||||
// E.g. if the first DWORD of a packet is 0x30107902:
|
||||
//
|
||||
// 001100 000001000001111 001 00000010
|
||||
// | | | |____ Skip 2 packets to find the next one for this stream
|
||||
// | | |___________ XMA2 signature (always 001)
|
||||
// | |_____________________ First frame starts 527 bits into packet
|
||||
// |________________________________ Packet contains 12 frames
|
||||
|
||||
|
||||
// Helper functions to extract the fields above from an XMA packet. (Note that
|
||||
// the bitfields cannot be read directly on little-endian architectures such as
|
||||
// the Intel x86, as they are laid out in big-endian form.)
|
||||
|
||||
__inline DWORD GetXmaPacketFrameCount(__in_bcount(1) const BYTE* pPacket)
|
||||
{
|
||||
return (DWORD)(pPacket[0] >> 2);
|
||||
}
|
||||
|
||||
__inline DWORD GetXmaPacketFirstFrameOffsetInBits(__in_bcount(3) const BYTE* pPacket)
|
||||
{
|
||||
return ((DWORD)(pPacket[0] & 0x3) << 13) |
|
||||
((DWORD)(pPacket[1]) << 5) |
|
||||
((DWORD)(pPacket[2]) >> 3);
|
||||
}
|
||||
|
||||
__inline DWORD GetXmaPacketMetadata(__in_bcount(3) const BYTE* pPacket)
|
||||
{
|
||||
return (DWORD)(pPacket[2] & 0x7);
|
||||
}
|
||||
|
||||
__inline DWORD GetXmaPacketSkipCount(__in_bcount(4) const BYTE* pPacket)
|
||||
{
|
||||
return (DWORD)(pPacket[3]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* XMA frame structure
|
||||
***************************************************************************/
|
||||
|
||||
// There is no way to represent the XMA frame as a C struct, since it is a
|
||||
// variable-sized string of bits that need not be stored at a byte-aligned
|
||||
// position in memory. This is the layout:
|
||||
//
|
||||
// XMAFRAME
|
||||
// {
|
||||
// LengthInBits: A 15-bit number representing the length of this frame.
|
||||
// XmaData: Encoded XMA data; its size in bits is (LengthInBits - 15).
|
||||
// }
|
||||
|
||||
// Size in bits of the frame's initial LengthInBits field
|
||||
#define XMA_BITS_IN_FRAME_LENGTH_FIELD 15
|
||||
|
||||
// Special LengthInBits value that marks an invalid final frame
|
||||
#define XMA_FINAL_FRAME_MARKER 0x7FFF
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* XMA helper functions
|
||||
***************************************************************************/
|
||||
|
||||
// We define a local ASSERT macro to equal the global one if it exists.
|
||||
// You can define XMA2DEFS_ASSERT in advance to override this default.
|
||||
#ifndef XMA2DEFS_ASSERT
|
||||
#ifdef ASSERT
|
||||
#define XMA2DEFS_ASSERT ASSERT
|
||||
#else
|
||||
#define XMA2DEFS_ASSERT(a) /* No-op by default */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// GetXmaBlockContainingSample: Use a given seek table to find the XMA block
|
||||
// containing a given decoded sample. Note that the seek table entries in an
|
||||
// XMA file are stored in big-endian form and may need to be converted prior
|
||||
// to calling this function.
|
||||
|
||||
__inline HRESULT GetXmaBlockContainingSample
|
||||
(
|
||||
DWORD nBlockCount, // Blocks in the file (= seek table entries)
|
||||
__in_ecount(nBlockCount) const DWORD* pSeekTable, // Pointer to the seek table data
|
||||
DWORD nDesiredSample, // Decoded sample to locate
|
||||
__out DWORD* pnBlockContainingSample, // Index of the block containing the sample
|
||||
__out DWORD* pnSampleOffsetWithinBlock // Position of the sample in this block
|
||||
)
|
||||
{
|
||||
DWORD nPreviousTotalSamples = 0;
|
||||
DWORD nBlock;
|
||||
DWORD nTotalSamplesSoFar;
|
||||
|
||||
XMA2DEFS_ASSERT(pSeekTable);
|
||||
XMA2DEFS_ASSERT(pnBlockContainingSample);
|
||||
XMA2DEFS_ASSERT(pnSampleOffsetWithinBlock);
|
||||
|
||||
for (nBlock = 0; nBlock < nBlockCount; ++nBlock)
|
||||
{
|
||||
nTotalSamplesSoFar = pSeekTable[nBlock];
|
||||
if (nTotalSamplesSoFar > nDesiredSample)
|
||||
{
|
||||
*pnBlockContainingSample = nBlock;
|
||||
*pnSampleOffsetWithinBlock = nDesiredSample - nPreviousTotalSamples;
|
||||
return S_OK;
|
||||
}
|
||||
nPreviousTotalSamples = nTotalSamplesSoFar;
|
||||
}
|
||||
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
|
||||
// GetXmaFrameLengthInBits: Reads a given frame's LengthInBits field.
|
||||
|
||||
__inline DWORD GetXmaFrameLengthInBits
|
||||
(
|
||||
__in_bcount(nBitPosition / 8 + 3)
|
||||
__in const BYTE* pPacket, // Pointer to XMA packet[s] containing the frame
|
||||
DWORD nBitPosition // Bit offset of the frame within this packet
|
||||
)
|
||||
{
|
||||
DWORD nRegion;
|
||||
DWORD nBytePosition = nBitPosition / 8;
|
||||
DWORD nBitOffset = nBitPosition % 8;
|
||||
|
||||
if (nBitOffset < 2) // Only need to read 2 bytes (and might not be safe to read more)
|
||||
{
|
||||
nRegion = (DWORD)(pPacket[nBytePosition+0]) << 8 |
|
||||
(DWORD)(pPacket[nBytePosition+1]);
|
||||
return (nRegion >> (1 - nBitOffset)) & 0x7FFF; // Last 15 bits
|
||||
}
|
||||
else // Need to read 3 bytes
|
||||
{
|
||||
nRegion = (DWORD)(pPacket[nBytePosition+0]) << 16 |
|
||||
(DWORD)(pPacket[nBytePosition+1]) << 8 |
|
||||
(DWORD)(pPacket[nBytePosition+2]);
|
||||
return (nRegion >> (9 - nBitOffset)) & 0x7FFF; // Last 15 bits
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GetXmaFrameBitPosition: Calculates the bit offset of a given frame within
|
||||
// an XMA block or set of blocks. Returns 0 on failure.
|
||||
|
||||
__inline DWORD GetXmaFrameBitPosition
|
||||
(
|
||||
__in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s]
|
||||
DWORD nXmaDataBytes, // Size of pXmaData in bytes
|
||||
DWORD nStreamIndex, // Stream within which to seek
|
||||
DWORD nDesiredFrame // Frame sought
|
||||
)
|
||||
{
|
||||
const BYTE* pCurrentPacket;
|
||||
DWORD nPacketsExamined = 0;
|
||||
DWORD nFrameCountSoFar = 0;
|
||||
DWORD nFramesToSkip;
|
||||
DWORD nFrameBitOffset;
|
||||
|
||||
XMA2DEFS_ASSERT(pXmaData);
|
||||
XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0);
|
||||
|
||||
// Get the first XMA packet belonging to the desired stream, relying on the
|
||||
// fact that the first packets for each stream are in consecutive order at
|
||||
// the beginning of an XMA block.
|
||||
|
||||
pCurrentPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET;
|
||||
for (;;)
|
||||
{
|
||||
// If we have exceeded the size of the XMA data, return failure
|
||||
if (pCurrentPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the current packet contains the frame we are looking for...
|
||||
if (nFrameCountSoFar + GetXmaPacketFrameCount(pCurrentPacket) > nDesiredFrame)
|
||||
{
|
||||
// See how many frames in this packet we need to skip to get to it
|
||||
XMA2DEFS_ASSERT(nDesiredFrame >= nFrameCountSoFar);
|
||||
nFramesToSkip = nDesiredFrame - nFrameCountSoFar;
|
||||
|
||||
// Get the bit offset of the first frame in this packet
|
||||
nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pCurrentPacket);
|
||||
|
||||
// Advance nFrameBitOffset to the frame of interest
|
||||
while (nFramesToSkip--)
|
||||
{
|
||||
nFrameBitOffset += GetXmaFrameLengthInBits(pCurrentPacket, nFrameBitOffset);
|
||||
}
|
||||
|
||||
// The bit offset to return is the number of bits from pXmaData to
|
||||
// pCurrentPacket plus the bit offset of the frame of interest
|
||||
return (DWORD)(pCurrentPacket - pXmaData) * 8 + nFrameBitOffset;
|
||||
}
|
||||
|
||||
// If we haven't found the right packet yet, advance our counters
|
||||
++nPacketsExamined;
|
||||
nFrameCountSoFar += GetXmaPacketFrameCount(pCurrentPacket);
|
||||
|
||||
// And skip to the next packet belonging to the same stream
|
||||
pCurrentPacket += XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pCurrentPacket) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GetLastXmaFrameBitPosition: Calculates the bit offset of the last complete
|
||||
// frame in an XMA block or set of blocks.
|
||||
|
||||
__inline DWORD GetLastXmaFrameBitPosition
|
||||
(
|
||||
__in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s]
|
||||
DWORD nXmaDataBytes, // Size of pXmaData in bytes
|
||||
DWORD nStreamIndex // Stream within which to seek
|
||||
)
|
||||
{
|
||||
const BYTE* pLastPacket;
|
||||
DWORD nBytesToNextPacket;
|
||||
DWORD nFrameBitOffset;
|
||||
DWORD nFramesInLastPacket;
|
||||
|
||||
XMA2DEFS_ASSERT(pXmaData);
|
||||
XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0);
|
||||
XMA2DEFS_ASSERT(nXmaDataBytes >= XMA_BYTES_PER_PACKET * (nStreamIndex + 1));
|
||||
|
||||
// Get the first XMA packet belonging to the desired stream, relying on the
|
||||
// fact that the first packets for each stream are in consecutive order at
|
||||
// the beginning of an XMA block.
|
||||
pLastPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET;
|
||||
|
||||
// Search for the last packet belonging to the desired stream
|
||||
for (;;)
|
||||
{
|
||||
nBytesToNextPacket = XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pLastPacket) + 1);
|
||||
XMA2DEFS_ASSERT(nBytesToNextPacket);
|
||||
if (pLastPacket + nBytesToNextPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes)
|
||||
{
|
||||
break; // The next packet would extend beyond the end of pXmaData
|
||||
}
|
||||
pLastPacket += nBytesToNextPacket;
|
||||
}
|
||||
|
||||
// The last packet can sometimes have no seekable frames, in which case we
|
||||
// have to use the previous one
|
||||
if (GetXmaPacketFrameCount(pLastPacket) == 0)
|
||||
{
|
||||
pLastPacket -= nBytesToNextPacket;
|
||||
}
|
||||
|
||||
// Found the last packet. Get the bit offset of its first frame.
|
||||
nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pLastPacket);
|
||||
|
||||
// Traverse frames until we reach the last one
|
||||
nFramesInLastPacket = GetXmaPacketFrameCount(pLastPacket);
|
||||
while (--nFramesInLastPacket)
|
||||
{
|
||||
nFrameBitOffset += GetXmaFrameLengthInBits(pLastPacket, nFrameBitOffset);
|
||||
}
|
||||
|
||||
// The bit offset to return is the number of bits from pXmaData to
|
||||
// pLastPacket plus the offset of the last frame in this packet.
|
||||
return (DWORD)(pLastPacket - pXmaData) * 8 + nFrameBitOffset;
|
||||
}
|
||||
|
||||
|
||||
// GetXmaDecodePositionForSample: Obtains the information needed to make the
|
||||
// decoder generate audio starting at a given sample position relative to the
|
||||
// beginning of the given XMA block: the bit offset of the appropriate frame,
|
||||
// and the right subframe within that frame. This data can be passed directly
|
||||
// to the XMAPlaybackSetDecodePosition function.
|
||||
|
||||
__inline HRESULT GetXmaDecodePositionForSample
|
||||
(
|
||||
__in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s]
|
||||
DWORD nXmaDataBytes, // Size of pXmaData in bytes
|
||||
DWORD nStreamIndex, // Stream within which to seek
|
||||
DWORD nDesiredSample, // Sample sought
|
||||
__out DWORD* pnBitOffset, // Returns the bit offset within pXmaData of
|
||||
// the frame containing the sample sought
|
||||
__out DWORD* pnSubFrame // Returns the subframe containing the sample
|
||||
)
|
||||
{
|
||||
DWORD nDesiredFrame = nDesiredSample / XMA_SAMPLES_PER_FRAME;
|
||||
DWORD nSubFrame = (nDesiredSample % XMA_SAMPLES_PER_FRAME) / XMA_SAMPLES_PER_SUBFRAME;
|
||||
DWORD nBitOffset = GetXmaFrameBitPosition(pXmaData, nXmaDataBytes, nStreamIndex, nDesiredFrame);
|
||||
|
||||
XMA2DEFS_ASSERT(pnBitOffset);
|
||||
XMA2DEFS_ASSERT(pnSubFrame);
|
||||
|
||||
if (nBitOffset)
|
||||
{
|
||||
*pnBitOffset = nBitOffset;
|
||||
*pnSubFrame = nSubFrame;
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GetXmaSampleRate: Obtains the legal XMA sample rate (24, 32, 44.1 or 48Khz)
|
||||
// corresponding to a generic sample rate.
|
||||
|
||||
__inline DWORD GetXmaSampleRate(DWORD dwGeneralRate)
|
||||
{
|
||||
DWORD dwXmaRate = 48000; // Default XMA rate for all rates above 44100Hz
|
||||
|
||||
if (dwGeneralRate <= 24000) dwXmaRate = 24000;
|
||||
else if (dwGeneralRate <= 32000) dwXmaRate = 32000;
|
||||
else if (dwGeneralRate <= 44100) dwXmaRate = 44100;
|
||||
|
||||
return dwXmaRate;
|
||||
}
|
||||
|
||||
|
||||
// Functions to convert between WAVEFORMATEXTENSIBLE channel masks (combinations
|
||||
// of the SPEAKER_xxx flags defined in audiodefs.h) and XMA channel masks (which
|
||||
// are limited to eight possible speaker positions: left, right, center, low
|
||||
// frequency, side left, side right, back left and back right).
|
||||
|
||||
__inline DWORD GetStandardChannelMaskFromXmaMask(BYTE bXmaMask)
|
||||
{
|
||||
DWORD dwStandardMask = 0;
|
||||
|
||||
if (bXmaMask & XMA_SPEAKER_LEFT) dwStandardMask |= SPEAKER_FRONT_LEFT;
|
||||
if (bXmaMask & XMA_SPEAKER_RIGHT) dwStandardMask |= SPEAKER_FRONT_RIGHT;
|
||||
if (bXmaMask & XMA_SPEAKER_CENTER) dwStandardMask |= SPEAKER_FRONT_CENTER;
|
||||
if (bXmaMask & XMA_SPEAKER_LFE) dwStandardMask |= SPEAKER_LOW_FREQUENCY;
|
||||
if (bXmaMask & XMA_SPEAKER_LEFT_SURROUND) dwStandardMask |= SPEAKER_SIDE_LEFT;
|
||||
if (bXmaMask & XMA_SPEAKER_RIGHT_SURROUND) dwStandardMask |= SPEAKER_SIDE_RIGHT;
|
||||
if (bXmaMask & XMA_SPEAKER_LEFT_BACK) dwStandardMask |= SPEAKER_BACK_LEFT;
|
||||
if (bXmaMask & XMA_SPEAKER_RIGHT_BACK) dwStandardMask |= SPEAKER_BACK_RIGHT;
|
||||
|
||||
return dwStandardMask;
|
||||
}
|
||||
|
||||
__inline BYTE GetXmaChannelMaskFromStandardMask(DWORD dwStandardMask)
|
||||
{
|
||||
BYTE bXmaMask = 0;
|
||||
|
||||
if (dwStandardMask & SPEAKER_FRONT_LEFT) bXmaMask |= XMA_SPEAKER_LEFT;
|
||||
if (dwStandardMask & SPEAKER_FRONT_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT;
|
||||
if (dwStandardMask & SPEAKER_FRONT_CENTER) bXmaMask |= XMA_SPEAKER_CENTER;
|
||||
if (dwStandardMask & SPEAKER_LOW_FREQUENCY) bXmaMask |= XMA_SPEAKER_LFE;
|
||||
if (dwStandardMask & SPEAKER_SIDE_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_SURROUND;
|
||||
if (dwStandardMask & SPEAKER_SIDE_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_SURROUND;
|
||||
if (dwStandardMask & SPEAKER_BACK_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_BACK;
|
||||
if (dwStandardMask & SPEAKER_BACK_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_BACK;
|
||||
|
||||
return bXmaMask;
|
||||
}
|
||||
|
||||
|
||||
// LocalizeXma2Format: Modifies a XMA2WAVEFORMATEX structure in place to comply
|
||||
// with the current platform's byte-ordering rules (little- or big-endian).
|
||||
|
||||
__inline HRESULT LocalizeXma2Format(__inout XMA2WAVEFORMATEX* pXma2Format)
|
||||
{
|
||||
#define XMASWAP2BYTES(n) ((WORD)(((n) >> 8) | (((n) & 0xff) << 8)))
|
||||
#define XMASWAP4BYTES(n) ((DWORD)((n) >> 24 | (n) << 24 | ((n) & 0xff00) << 8 | ((n) & 0xff0000) >> 8))
|
||||
|
||||
if (pXma2Format->wfx.wFormatTag == WAVE_FORMAT_XMA2)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
else if (XMASWAP2BYTES(pXma2Format->wfx.wFormatTag) == WAVE_FORMAT_XMA2)
|
||||
{
|
||||
pXma2Format->wfx.wFormatTag = XMASWAP2BYTES(pXma2Format->wfx.wFormatTag);
|
||||
pXma2Format->wfx.nChannels = XMASWAP2BYTES(pXma2Format->wfx.nChannels);
|
||||
pXma2Format->wfx.nSamplesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nSamplesPerSec);
|
||||
pXma2Format->wfx.nAvgBytesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nAvgBytesPerSec);
|
||||
pXma2Format->wfx.nBlockAlign = XMASWAP2BYTES(pXma2Format->wfx.nBlockAlign);
|
||||
pXma2Format->wfx.wBitsPerSample = XMASWAP2BYTES(pXma2Format->wfx.wBitsPerSample);
|
||||
pXma2Format->wfx.cbSize = XMASWAP2BYTES(pXma2Format->wfx.cbSize);
|
||||
pXma2Format->NumStreams = XMASWAP2BYTES(pXma2Format->NumStreams);
|
||||
pXma2Format->ChannelMask = XMASWAP4BYTES(pXma2Format->ChannelMask);
|
||||
pXma2Format->SamplesEncoded = XMASWAP4BYTES(pXma2Format->SamplesEncoded);
|
||||
pXma2Format->BytesPerBlock = XMASWAP4BYTES(pXma2Format->BytesPerBlock);
|
||||
pXma2Format->PlayBegin = XMASWAP4BYTES(pXma2Format->PlayBegin);
|
||||
pXma2Format->PlayLength = XMASWAP4BYTES(pXma2Format->PlayLength);
|
||||
pXma2Format->LoopBegin = XMASWAP4BYTES(pXma2Format->LoopBegin);
|
||||
pXma2Format->LoopLength = XMASWAP4BYTES(pXma2Format->LoopLength);
|
||||
pXma2Format->BlockCount = XMASWAP2BYTES(pXma2Format->BlockCount);
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
return E_FAIL; // Not a recognizable XMA2 format
|
||||
}
|
||||
|
||||
#undef XMASWAP2BYTES
|
||||
#undef XMASWAP4BYTES
|
||||
}
|
||||
|
||||
|
||||
#endif // #ifndef __XMA2DEFS_INCLUDED__
|
||||
Vendored
-1
Submodule 3rdparty/cereal deleted from 42a45b6e15
Vendored
-1
Submodule 3rdparty/ffmpeg deleted from 7b7ae7b067
Vendored
-1
Submodule 3rdparty/hidapi deleted from ca39ce8939
Vendored
-1
Submodule 3rdparty/libpng deleted from 1dcba4d6eb
Vendored
-1158
File diff suppressed because it is too large
Load Diff
Vendored
-1
Submodule 3rdparty/pugixml deleted from 257fbb4e1b
Vendored
-6698
File diff suppressed because it is too large
Load Diff
Vendored
-3301
File diff suppressed because it is too large
Load Diff
Vendored
-1
Submodule 3rdparty/zlib deleted from cacf7f1d4e
+3
-39
@@ -1,11 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
option(WITH_GDB "WITH_GDB" OFF)
|
||||
option(WITHOUT_LLVM "WITHOUT_LLVM" OFF)
|
||||
|
||||
if (WITH_GDB)
|
||||
add_definitions(-DWITH_GDB_DEBUGGER)
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
set(ASMJIT_STATIC TRUE)
|
||||
|
||||
@@ -18,42 +11,13 @@ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message( FATAL_ERROR "RPCS3 can only be compiled on 64-bit platforms." )
|
||||
endif()
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if (CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
||||
endif()
|
||||
|
||||
add_definitions(-DCMAKE_BUILD)
|
||||
|
||||
# We use libpng's static library and don't need to build the shared library and run the tests
|
||||
set(PNG_SHARED OFF CACHE BOOL "Build shared lib." FORCE)
|
||||
set(PNG_TESTS OFF CACHE BOOL "Build tests." FORCE)
|
||||
|
||||
# Select the version of libpng to use, default is builtin
|
||||
if (NOT USE_SYSTEM_LIBPNG)
|
||||
add_subdirectory( 3rdparty/libpng )
|
||||
endif()
|
||||
|
||||
option(VULKAN_PREBUILT "" OFF)
|
||||
|
||||
add_subdirectory( asmjit )
|
||||
add_subdirectory( libpng )
|
||||
# TODO: do real installation, including copying directory structure
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PROJECT_BINARY_DIR}/bin")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${PROJECT_BINARY_DIR}/bin")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${PROJECT_BINARY_DIR}/bin")
|
||||
|
||||
add_subdirectory( Vulkan )
|
||||
add_subdirectory( rpcs3 )
|
||||
|
||||
include_directories(3rdparty/hidapi/hidapi)
|
||||
if(APPLE)
|
||||
add_subdirectory(3rdparty/hidapi/mac)
|
||||
#list(APPEND LIBS hidapi)
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
add_subdirectory(3rdparty/hidapi/linux)
|
||||
elseif(MSVC)
|
||||
add_subdirectory(3rdparty/hidapi/windows)
|
||||
else()
|
||||
add_subdirectory(3rdparty/hidapi/libusb)
|
||||
#list(APPEND LIBS hidapi-libusb)
|
||||
endif()
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
Before getting started using the emulator, read the [Quickstart Guide](https://rpcs3.net/quickstart) and the [FAQ](https://rpcs3.net/faq). After reading those, if you need support, visit our [Forums](https://forums.rpcs3.net).
|
||||
|
||||
# Issue Reporting
|
||||
|
||||
**The GitHub Issue Tracker is not the place to ask for support or to submit [Game Compatibility](https://rpcs3.net/compatibility) reports.** Requests for support or incorrect reports will be closed. If you are not sure whether the issue you want to report is an actual issue that is not yet known, please use the forums to submit such report.
|
||||
|
||||
**Before reporting an issue:**
|
||||
- Check if your system matches all the minimum requirements listed in the [Quickstart Guide](https://rpcs3.net/quickstart);
|
||||
- Check if the issue is meaningful for the team (e.g. The Last of Us doesn't work is obvious and therefore useless);
|
||||
- Search older issues/forum threads to see if your issue was already submitted.
|
||||
- Use understandable English. It doesn't need to be perfect, but clear enough to understand your message.
|
||||
- While reporting issues, don't forget to include details about your system (OS, CPU, GPU, etc.), as well as the RPCS3.log file.
|
||||
|
||||
Submitting your test results for Commercial Games must be done on our forums. Please read the [Game Compatibility](https://github.com/RPCS3/rpcs3/wiki/Game-Compatibility) wiki page before doing so.
|
||||
|
||||
# Contributing
|
||||
|
||||
Check the [Coding Style Guidelines](https://github.com/RPCS3/rpcs3/wiki/Coding-Style), [Roadmap](https://github.com/RPCS3/rpcs3/wiki/Roadmap) and [Developer Information](https://github.com/RPCS3/rpcs3/wiki/Developer-Information). If you have any questions, hit us up on our [Discord Server](https://discord.me/RPCS3) in the **#development** channel.
|
||||
@@ -3,104 +3,48 @@ RPCS3
|
||||
|
||||
[](https://travis-ci.org/RPCS3/rpcs3)
|
||||
[](https://ci.appveyor.com/project/rpcs3/rpcs3/branch/master)
|
||||
[](https://scan.coverity.com/projects/3960)
|
||||
[](https://coveralls.io/r/RPCS3/rpcs3)
|
||||
|
||||
The world's first open-source PlayStation 3 emulator/debugger written in C++ for Windows and Linux.
|
||||
An open-source PlayStation 3 emulator/debugger written in C++.
|
||||
|
||||
You can find some basic information in our [**website**](https://rpcs3.net/).
|
||||
For discussion about this emulator and PS3 emulation please visit our [**forums**](https://forums.rpcs3.net) and our [**Discord server**](https://discord.me/RPCS3).
|
||||
|
||||
[**Support Lead Developers Nekotekina and kd-11 on Patreon**](https://www.patreon.com/Nekotekina)
|
||||
You can find some basic information in the [FAQ](https://github.com/RPCS3/rpcs3/wiki/FAQ). For discussion about this emulator and PS3 emulation please visit the [official forums](http://www.emunewz.net/forum/forumdisplay.php?fid=162).
|
||||
|
||||
|
||||
## Development
|
||||
### Development
|
||||
|
||||
If you want to contribute please take a look at the [Coding Style](https://github.com/RPCS3/rpcs3/wiki/Coding-Style), [Roadmap](https://github.com/RPCS3/rpcs3/wiki/Roadmap) and [Developer Information](https://github.com/RPCS3/rpcs3/wiki/Developer-Information) pages. You should as well contact any of the developers in the forums or in Discord in order to know more about the current situation of the emulator.
|
||||
If you want to contribute please take a took at the [Coding Style](https://github.com/RPCS3/rpcs3/wiki/Coding-Style), [Roadmap](https://github.com/RPCS3/rpcs3/wiki/Roadmap) and [Developer Information](https://github.com/RPCS3/rpcs3/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current situation of the emulator.
|
||||
|
||||
|
||||
## Dependencies
|
||||
### Dependencies
|
||||
|
||||
### Windows
|
||||
* [Visual Studio 2015](https://www.visualstudio.com/vs/older-downloads/)
|
||||
__Windows__
|
||||
* [Visual Studio 2015](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx)
|
||||
* [Visual C++ Redistributable Packages for Visual Studio 2015](http://www.microsoft.com/en-us/download/details.aspx?id=48145)
|
||||
* [Cmake 3.1.0+](https://www.cmake.org/download/) (required; add to PATH)
|
||||
* [Python 3.3+](https://www.python.org/downloads/) (required; add to PATH)
|
||||
* [Qt 5.8+](https://www.qt.io/download-open-source/) (required; add QTDIR `<QtInstallFolder>\5.8\msvc2015_64\` environment variable if you do not want to use the Visual Studio Qt Plugin)
|
||||
* [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2015) (optional; see above)
|
||||
* [Cmake 3.1.0+](http://www.cmake.org/download/) (optional, required only for LLVM build; add to PATH)
|
||||
* [Python](https://www.python.org/downloads/) (optional, required only for LLVM build; add to PATH)
|
||||
|
||||
### Linux
|
||||
* [Qt 5.7+](https://www.qt.io/download-open-source/)
|
||||
* GCC 5.1+ or Clang 3.5.0+ ([not GCC 6.1](https://github.com/RPCS3/rpcs3/issues/1691))
|
||||
* Debian & Ubuntu: `sudo apt-get install cmake build-essential libasound2-dev libpulse-dev libopenal-dev libglew-dev zlib1g-dev libedit-dev libvulkan-dev libudev-dev git qt5-default`
|
||||
* Arch: `sudo pacman -S glew openal cmake llvm qt5-base`
|
||||
* Fedora: `sudo dnf install cmake glew glew-devel libatomic libudev-devel openal-devel qt5-devel vulkan-devel`
|
||||
* OpenSUSE: `sudo zypper install git cmake libasound2 libpulse-devel openal-soft-devel glew-devel zlib-devel libedit-devel vulkan-devel libudev-devel libqt5-qtbase-devel libevdev-devel`
|
||||
**If you have a NVIDIA GPU, you may need to install the libglvnd package.**
|
||||
__Linux__
|
||||
* GCC 5.1+ or Clang 3.5.0+
|
||||
* Debian & Ubuntu: `sudo apt-get install libopenal-dev libwxgtk3.0-dev build-essential libglew-dev`
|
||||
* Arch: `sudo pacman -S glew openal wxgtk cmake llvm`
|
||||
|
||||
### MacOS
|
||||
MacOS is not supported at this moment because it doesn't meet system requirements (OpenGL 4.3)
|
||||
__Mac OSX__
|
||||
* Xcode 6+ (tested with Xcode 6.4)
|
||||
* Install with Homebrew: `brew install glew llvm qt cmake`
|
||||
* Install with Homebrew: `brew install glew wxwidgets` (add `llvm36` to that list if you want to build with ppu jit)
|
||||
* Remove '-framework QuickTime' from '_ldflags' in /usr/local/bin/wx-config
|
||||
|
||||
|
||||
## Building on Windows:
|
||||
To initialize the repository don't forget to execute `git submodule update --init` to pull the submodules.
|
||||
|
||||
### Configuring Qt
|
||||
|
||||
*If you're using Visual Studio 2017 without Qt plugin support (or simply dont want to use it):*
|
||||
1) Add `QTDIR` environment variable and set it to `<QtInstallFolder>\5.8\msvc2015_64\` </br>
|
||||
|
||||
|
||||
Open `rpcs3.sln`
|
||||
|
||||
|
||||
*If you wish to use the Visual Studio plugin for Qt:* </br>
|
||||
1) Go to the Qt5 menu and edit Qt5 options. Add the path to your Qt installation with compiler e.g. `C:\Qt\5.8\msvc2015_64`. </br>
|
||||
2) While selecting the rpcs3qt project, go to Qt5->Project Setting and select the version you added.
|
||||
|
||||
### Building the projects
|
||||
You may want to download precompiled [LLVM lib](https://drive.google.com/uc?export=download&id=0B8A6NaxhQAGRY2k3Q2Yya05lcm8) and extract to root rpcs3 folder (which contains `rpcs3.sln`), as well as download and extract [additional libs](https://drive.google.com/uc?export=download&id=0B8A6NaxhQAGRaHYxV3VOS2d0a0U) to `lib\%CONFIGURATION%-x64\` to speed up compilation time (unoptimised/debug libs are currently not available precompiled).
|
||||
|
||||
If you're not using precompiled libs, build the projects in *__BUILD_BEFORE* folder: right-click on every project > *Build*. </br></br>
|
||||
|
||||
`Build > Build Solution`</br>
|
||||
|
||||
|
||||
|
||||
## Building on Linux & Mac OS:
|
||||
|
||||
1) `git clone https://github.com/RPCS3/rpcs3.git` </br>
|
||||
2) `cd rpcs3/` </br>
|
||||
3) `git submodule update --init` </br>
|
||||
4) `cd ../ && mkdir rpcs3_build && cd rpcs3_build`
|
||||
4) `cmake ../rpcs3/ && make GitVersion && make` </br>
|
||||
5) Run RPCS3 with `./bin/rpcs3` </br>
|
||||
|
||||
If you are on MacOS and want to build with brew llvm and qt don't forget to add the following environment variables
|
||||
|
||||
* `LLVM_DIR=/usr/local/opt/llvm/` (or wherever llvm was installed).
|
||||
* `Qt5_DIR=/usr/local/opt/qt/lib/cmake/Qt5` (or wherever qt was installed).
|
||||
### Building
|
||||
|
||||
To initialize the repository don't forget to execute `git submodule update --init` to pull the wxWidgets source.
|
||||
* __Windows__:
|
||||
Open the *.SLN* file, and press *Build* > *Clean Solution*, then *Build Solution*. *Rebuild* may not work correctly.
|
||||
* __Linux & Mac OSX__:
|
||||
If you want to build with LLVM, then LLVM 3.6.2 is required.
|
||||
`cd rpcs3 && cmake CMakeLists.txt && make && cd ../` then run with `cd bin && ./rpcs3`.
|
||||
If you are on OSX and want to build with llvm don't forget to add `-DLLVM_DIR=/usr/local/opt/llvm36/lib/llvm-3.6/share/llvm/cmake` (or wherever llvm brew was installed) to cmake invocation.
|
||||
When using GDB, configure it to ignore SIGSEGV signal (`handle SIGSEGV nostop noprint`).
|
||||
|
||||
|
||||
## CMake Build Options (Linux & Mac OS)
|
||||
|
||||
- ```-DUSE_SYSTEM_LIBPNG=ON/OFF``` (default = *OFF*)
|
||||
Build against the shared libpng instead of using the builtin one. libpng 1.6+ highly recommended. Try this option if you get version conflict errors or only see black game icons.
|
||||
|
||||
- ```-DUSE_SYSTEM_FFMPEG=ON/OFF``` (default = *OFF*)
|
||||
Build against the shared ffmpeg libraries instead of using the builtin patched version. Try this if the builtin version breaks the OpenGL renderer for you.
|
||||
|
||||
- ```-DWITHOUT_LLVM=ON/OFF``` (default = *OFF*)
|
||||
This forces RPCS3 to build without LLVM, not recommended.
|
||||
|
||||
- ```-DWITH_GDB=ON/OFF``` (default = *OFF*)
|
||||
This Builds RPCS3 with support for debugging PS3 games using gdb.
|
||||
|
||||
- ```-DUSE_VULKAN=ON/OFF``` (default = *ON*)
|
||||
This builds RPCS3 with Vulkan support.
|
||||
|
||||
## License
|
||||
|
||||
Most files are licensed under the terms of GNU GPLv2 License, see LICENSE file for details. Some files may be licensed differently, check appropriate file headers for details.
|
||||
### Support
|
||||
* [Donate by PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MPJ3S9XQXCE3G)
|
||||
|
||||
+603
-961
File diff suppressed because it is too large
Load Diff
@@ -1,150 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Atomic.h"
|
||||
#include <memory>
|
||||
|
||||
// Unfinished. Only std::default_delete will work as expected.
|
||||
template<typename T, typename D>
|
||||
class atomic_ptr_base : D
|
||||
{
|
||||
protected:
|
||||
atomic_t<T*> m_ptr;
|
||||
|
||||
constexpr atomic_ptr_base(T* ptr)
|
||||
: m_ptr(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
~atomic_ptr_base()
|
||||
{
|
||||
if (m_ptr)
|
||||
{
|
||||
(*this)(m_ptr.load());
|
||||
}
|
||||
}
|
||||
|
||||
D& get_deleter()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const D& get_deleter() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// Simple atomic pointer with unique ownership. Draft, unfinished.
|
||||
template<typename T, typename D = std::default_delete<T>>
|
||||
class atomic_ptr final : atomic_ptr_base<T, D>
|
||||
{
|
||||
using base = atomic_ptr_base<T, D>;
|
||||
|
||||
static_assert(sizeof(T*) == sizeof(base), "atomic_ptr<> error: invalid deleter (empty class expected)");
|
||||
|
||||
public:
|
||||
constexpr atomic_ptr()
|
||||
: base(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr atomic_ptr(std::nullptr_t)
|
||||
: base(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
explicit atomic_ptr(T* ptr)
|
||||
: base(ptr)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T2, typename = std::enable_if_t<std::is_convertible<T2, T>::value>>
|
||||
atomic_ptr(std::unique_ptr<T2, D>&& ptr)
|
||||
: base(ptr.release())
|
||||
{
|
||||
}
|
||||
|
||||
atomic_ptr& operator =(std::nullptr_t)
|
||||
{
|
||||
if (T* old = base::m_ptr.exchange(nullptr))
|
||||
{
|
||||
this->get_deleter()(old);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T2, typename = std::enable_if_t<std::is_convertible<T2, T>::value>>
|
||||
atomic_ptr& operator =(std::unique_ptr<T2, D>&& ptr)
|
||||
{
|
||||
if (T* old = base::m_ptr.exchange(ptr.release()))
|
||||
{
|
||||
this->get_deleter()(old);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(std::unique_ptr<T, D>& ptr)
|
||||
{
|
||||
ptr.reset(base::m_ptr.exchange(ptr.release()));
|
||||
}
|
||||
|
||||
std::add_lvalue_reference_t<T> operator *() const
|
||||
{
|
||||
return *base::m_ptr;
|
||||
}
|
||||
|
||||
T* operator ->() const
|
||||
{
|
||||
return base::m_ptr;
|
||||
}
|
||||
|
||||
T* get() const
|
||||
{
|
||||
return base::m_ptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return base::m_ptr != nullptr;
|
||||
}
|
||||
|
||||
T* release() const
|
||||
{
|
||||
return base::m_ptr.exchange(0);
|
||||
}
|
||||
|
||||
void reset(T* ptr = nullptr)
|
||||
{
|
||||
if (T* old = base::m_ptr.exchange(ptr))
|
||||
{
|
||||
this->get_deleter()(old);
|
||||
}
|
||||
}
|
||||
|
||||
// Steal the pointer from `ptr`, convert old value to unique_ptr
|
||||
std::unique_ptr<T, D> exchange(std::unique_ptr<T, D>&& ptr)
|
||||
{
|
||||
return std::unique_ptr<T, D>(base::m_ptr.exchange(ptr.release()));
|
||||
}
|
||||
|
||||
// If pointer is null, steal it from `ptr`
|
||||
bool test_and_swap(std::unique_ptr<T, D>&& ptr)
|
||||
{
|
||||
if (base::m_ptr.compare_and_swap_test(nullptr, ptr.get()))
|
||||
{
|
||||
ptr.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename D>
|
||||
class atomic_ptr<T[], D> final : atomic_ptr_base<T[], D>
|
||||
{
|
||||
// TODO
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "stdafx.h"
|
||||
#include "AutoPause.h"
|
||||
#include "Utilities/Log.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/state.h"
|
||||
|
||||
using namespace Debug;
|
||||
|
||||
std::unique_ptr<AutoPause> g_autopause;
|
||||
|
||||
AutoPause& AutoPause::getInstance(void)
|
||||
{
|
||||
if (!g_autopause)
|
||||
{
|
||||
g_autopause.reset(new AutoPause);
|
||||
}
|
||||
|
||||
return *g_autopause;
|
||||
}
|
||||
|
||||
//Still use binary format. Default Setting should be "disable all auto pause".
|
||||
AutoPause::AutoPause(void)
|
||||
{
|
||||
m_pause_function.reserve(16);
|
||||
m_pause_syscall.reserve(16);
|
||||
initialized = false;
|
||||
//Reload(false, false);
|
||||
Reload();
|
||||
}
|
||||
|
||||
//Notice: I would not allow to write the binary to file in this command.
|
||||
AutoPause::~AutoPause(void)
|
||||
{
|
||||
initialized = false;
|
||||
m_pause_function.clear();
|
||||
m_pause_syscall.clear();
|
||||
m_pause_function_enable = false;
|
||||
m_pause_syscall_enable = false;
|
||||
}
|
||||
|
||||
//Load Auto Pause Configuration from file "pause.bin"
|
||||
//This would be able to create in a GUI window.
|
||||
void AutoPause::Reload(void)
|
||||
{
|
||||
if (fs::is_file(fs::get_config_dir() + "pause.bin"))
|
||||
{
|
||||
m_pause_function.clear();
|
||||
m_pause_function.reserve(16);
|
||||
m_pause_syscall.clear();
|
||||
m_pause_syscall.reserve(16);
|
||||
|
||||
fs::file list(fs::get_config_dir() + "pause.bin");
|
||||
//System calls ID and Function calls ID are all u32 iirc.
|
||||
u32 num;
|
||||
size_t fmax = list.size();
|
||||
size_t fcur = 0;
|
||||
list.seek(0);
|
||||
while (fcur <= fmax - sizeof(u32))
|
||||
{
|
||||
list.read(&num, sizeof(u32));
|
||||
fcur += sizeof(u32);
|
||||
if (num == 0xFFFFFFFF) break;
|
||||
|
||||
if (num < 1024)
|
||||
{
|
||||
//Less than 1024 - be regarded as a system call.
|
||||
//emplace_back may not cause reductant move/copy operation.
|
||||
m_pause_syscall.emplace_back(num);
|
||||
LOG_WARNING(HLE, "Auto Pause: Find System Call ID 0x%x", num);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pause_function.emplace_back(num);
|
||||
LOG_WARNING(HLE, "Auto Pause: Find Function Call ID 0x%x", num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_pause_syscall_enable = rpcs3::config.misc.debug.auto_pause_syscall.value();
|
||||
m_pause_function_enable = rpcs3::config.misc.debug.auto_pause_func_call.value();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void AutoPause::TryPause(u32 code)
|
||||
{
|
||||
if (code < 1024)
|
||||
{
|
||||
//Would first check Enable setting. Then the list length.
|
||||
if ((!m_pause_syscall_enable)
|
||||
|| (m_pause_syscall.size() <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < m_pause_syscall.size(); ++i)
|
||||
{
|
||||
if (code == m_pause_syscall[i])
|
||||
{
|
||||
Emu.Pause();
|
||||
LOG_ERROR(HLE, "Auto Pause Triggered: System call 0x%x", code); // Used Error
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Well similiar.. Seperate the list caused by possible setting difference.
|
||||
if ((!m_pause_function_enable)
|
||||
|| (m_pause_function.size() <= 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < m_pause_function.size(); ++i)
|
||||
{
|
||||
if (code == m_pause_function[i])
|
||||
{
|
||||
Emu.Pause();
|
||||
LOG_ERROR(HLE, "Auto Pause Triggered: Function call 0x%x", code); // Used Error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
//Regarded as a Debugger Enchantment
|
||||
namespace Debug {
|
||||
//To store the pause function/call id, and let those pause there.
|
||||
//Would be with a GUI to configure those.
|
||||
struct AutoPause
|
||||
{
|
||||
std::vector<u32> m_pause_syscall;
|
||||
std::vector<u32> m_pause_function;
|
||||
bool initialized;
|
||||
bool m_pause_syscall_enable;
|
||||
bool m_pause_function_enable;
|
||||
|
||||
AutoPause();
|
||||
~AutoPause();
|
||||
public:
|
||||
static AutoPause& getInstance(void);
|
||||
|
||||
void Reload(void);
|
||||
|
||||
void TryPause(u32 code);
|
||||
};
|
||||
}
|
||||
+422
-291
File diff suppressed because it is too large
Load Diff
+53
-196
@@ -1,283 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
template<typename T, uint N>
|
||||
struct bf_base
|
||||
// BitField access helper class (N bits from I position), intended to be put in union
|
||||
template<typename T, u32 I, u32 N> class bf_t
|
||||
{
|
||||
using type = T;
|
||||
using vtype = simple_t<type>;
|
||||
using utype = typename std::make_unsigned<vtype>::type;
|
||||
// Checks
|
||||
static_assert(I < sizeof(T) * 8, "bf_t<> error: I out of bounds");
|
||||
static_assert(N < sizeof(T) * 8, "bf_t<> error: N out of bounds");
|
||||
static_assert(I + N <= sizeof(T) * 8, "bf_t<> error: values out of bounds");
|
||||
|
||||
// Datatype bitsize
|
||||
static constexpr uint bitmax = sizeof(T) * 8; static_assert(N - 1 < bitmax, "bf_base<> error: N out of bounds");
|
||||
|
||||
// Field bitsize
|
||||
static constexpr uint bitsize = N;
|
||||
// Underlying data type
|
||||
using type = typename std::remove_cv<T>::type;
|
||||
|
||||
// Value mask
|
||||
static constexpr utype vmask = static_cast<utype>(~utype{} >> (bitmax - bitsize));
|
||||
// Underlying value type (native endianness)
|
||||
using vtype = typename to_ne<type>::type;
|
||||
|
||||
// All ones mask
|
||||
static constexpr utype mask1 = static_cast<utype>(~utype{});
|
||||
// Mask of size N
|
||||
constexpr static vtype s_mask = (static_cast<vtype>(1) << N) - 1;
|
||||
|
||||
protected:
|
||||
// Underlying data member
|
||||
type m_data;
|
||||
};
|
||||
|
||||
// Bitfield accessor (N bits from I position, 0 is LSB)
|
||||
template<typename T, uint I, uint N>
|
||||
struct bf_t : bf_base<T, N>
|
||||
{
|
||||
using type = typename bf_t::type;
|
||||
using vtype = typename bf_t::vtype;
|
||||
using utype = typename bf_t::utype;
|
||||
// Conversion operator helper (uses SFINAE)
|
||||
template<typename T2, typename = void> struct converter {};
|
||||
|
||||
// Field offset
|
||||
static constexpr uint bitpos = I; static_assert(bitpos + N <= bf_t::bitmax, "bf_t<> error: I out of bounds");
|
||||
|
||||
// Get bitmask of size N, at I pos
|
||||
static constexpr utype data_mask()
|
||||
{
|
||||
return static_cast<utype>(static_cast<utype>(bf_t::mask1 >> (bf_t::bitmax - bf_t::bitsize)) << bitpos);
|
||||
}
|
||||
|
||||
// Bitfield extraction helper
|
||||
template<typename T2, typename = void>
|
||||
struct extract_impl
|
||||
{
|
||||
static_assert(!sizeof(T2), "bf_t<> error: Invalid type");
|
||||
};
|
||||
|
||||
template<typename T2>
|
||||
struct extract_impl<T2, std::enable_if_t<std::is_unsigned<T2>::value>>
|
||||
template<typename T2> struct converter<T2, std::enable_if_t<std::is_unsigned<T2>::value>>
|
||||
{
|
||||
// Load unsigned value
|
||||
static constexpr T2 extract(const T& data)
|
||||
static inline T2 convert(const type& data)
|
||||
{
|
||||
return static_cast<T2>((static_cast<utype>(data) >> bitpos) & bf_t::vmask);
|
||||
return (data >> I) & s_mask;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T2>
|
||||
struct extract_impl<T2, std::enable_if_t<std::is_signed<T2>::value>>
|
||||
template<typename T2> struct converter<T2, std::enable_if_t<std::is_signed<T2>::value>>
|
||||
{
|
||||
// Load signed value (sign-extended)
|
||||
static constexpr T2 extract(const T& data)
|
||||
static inline T2 convert(const type& data)
|
||||
{
|
||||
return static_cast<T2>(static_cast<vtype>(static_cast<utype>(data) << (bf_t::bitmax - bitpos - N)) >> (bf_t::bitmax - N));
|
||||
return data << (sizeof(T) * 8 - I - N) >> (sizeof(T) * 8 - N);
|
||||
}
|
||||
};
|
||||
|
||||
// Bitfield extraction
|
||||
static constexpr vtype extract(const T& data)
|
||||
public:
|
||||
// Assignment operator (store bitfield value)
|
||||
bf_t& operator =(vtype value)
|
||||
{
|
||||
return extract_impl<vtype>::extract(data);
|
||||
m_data = (m_data & ~(s_mask << I)) | (value & s_mask) << I;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Bitfield insertion
|
||||
static constexpr vtype insert(vtype value)
|
||||
// Conversion operator (load bitfield value)
|
||||
operator vtype() const
|
||||
{
|
||||
return static_cast<vtype>((value & bf_t::vmask) << bitpos);
|
||||
return converter<vtype>::convert(m_data);
|
||||
}
|
||||
|
||||
// Load bitfield value
|
||||
constexpr operator vtype() const
|
||||
// Get raw data with mask applied
|
||||
type unshifted() const
|
||||
{
|
||||
return extract(this->m_data);
|
||||
return (m_data & (s_mask << I));
|
||||
}
|
||||
|
||||
// Load raw data with mask applied
|
||||
constexpr T unshifted() const
|
||||
{
|
||||
return static_cast<T>(this->m_data & data_mask());
|
||||
}
|
||||
|
||||
// Optimized bool conversion (must be removed if inappropriate)
|
||||
explicit constexpr operator bool() const
|
||||
// Optimized bool conversion
|
||||
explicit operator bool() const
|
||||
{
|
||||
return unshifted() != 0;
|
||||
}
|
||||
|
||||
// Store bitfield value
|
||||
bf_t& operator =(vtype value)
|
||||
{
|
||||
this->m_data = static_cast<vtype>((this->m_data & ~data_mask()) | insert(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Postfix increment operator
|
||||
vtype operator ++(int)
|
||||
{
|
||||
utype result = *this;
|
||||
*this = static_cast<vtype>(result + 1);
|
||||
vtype result = *this;
|
||||
*this = result + 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Prefix increment operator
|
||||
bf_t& operator ++()
|
||||
{
|
||||
return *this = *this + 1;
|
||||
}
|
||||
|
||||
// Postfix decrement operator
|
||||
vtype operator --(int)
|
||||
{
|
||||
utype result = *this;
|
||||
*this = static_cast<vtype>(result - 1);
|
||||
vtype result = *this;
|
||||
*this = result - 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Prefix decrement operator
|
||||
bf_t& operator --()
|
||||
{
|
||||
return *this = *this - 1;
|
||||
}
|
||||
|
||||
// Addition assignment operator
|
||||
bf_t& operator +=(vtype right)
|
||||
{
|
||||
return *this = *this + right;
|
||||
}
|
||||
|
||||
// Subtraction assignment operator
|
||||
bf_t& operator -=(vtype right)
|
||||
{
|
||||
return *this = *this - right;
|
||||
}
|
||||
|
||||
// Multiplication assignment operator
|
||||
bf_t& operator *=(vtype right)
|
||||
{
|
||||
return *this = *this * right;
|
||||
}
|
||||
|
||||
// Bitwise AND assignment operator
|
||||
bf_t& operator &=(vtype right)
|
||||
{
|
||||
this->m_data &= static_cast<vtype>((static_cast<utype>(right) & bf_t::vmask) << bitpos);
|
||||
m_data &= (right & s_mask) << I;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Bitwise OR assignment operator
|
||||
bf_t& operator |=(vtype right)
|
||||
{
|
||||
this->m_data |= static_cast<vtype>((static_cast<utype>(right) & bf_t::vmask) << bitpos);
|
||||
m_data |= (right & s_mask) << I;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Bitwise XOR assignment operator
|
||||
bf_t& operator ^=(vtype right)
|
||||
{
|
||||
this->m_data ^= static_cast<vtype>((static_cast<utype>(right) & bf_t::vmask) << bitpos);
|
||||
m_data ^= (right & s_mask) << I;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// Field pack (concatenated from left to right)
|
||||
template<typename F = void, typename... Fields>
|
||||
struct cf_t : bf_base<typename F::type, F::bitsize + cf_t<Fields...>::bitsize>
|
||||
{
|
||||
using type = typename cf_t::type;
|
||||
using vtype = typename cf_t::vtype;
|
||||
using utype = typename cf_t::utype;
|
||||
template<typename T, u32 I, u32 N> using bf_be_t = bf_t<be_t<T>, I, N>;
|
||||
|
||||
// Get disjunction of all "data" masks of concatenated values
|
||||
static constexpr vtype data_mask()
|
||||
{
|
||||
return static_cast<vtype>(F::data_mask() | cf_t<Fields...>::data_mask());
|
||||
}
|
||||
|
||||
// Extract all bitfields and concatenate
|
||||
static constexpr vtype extract(const type& data)
|
||||
{
|
||||
return static_cast<vtype>(static_cast<utype>(F::extract(data)) << cf_t<Fields...>::bitsize | cf_t<Fields...>::extract(data));
|
||||
}
|
||||
|
||||
// Split bitfields and insert them
|
||||
static constexpr vtype insert(vtype value)
|
||||
{
|
||||
return static_cast<vtype>(F::insert(value >> cf_t<Fields...>::bitsize) | cf_t<Fields...>::insert(value));
|
||||
}
|
||||
|
||||
// Load value
|
||||
constexpr operator vtype() const
|
||||
{
|
||||
return extract(this->m_data);
|
||||
}
|
||||
|
||||
// Store value
|
||||
cf_t& operator =(vtype value)
|
||||
{
|
||||
this->m_data = (this->m_data & ~data_mask()) | insert(value);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// Empty field pack (recursion terminator)
|
||||
template<>
|
||||
struct cf_t<void>
|
||||
{
|
||||
static constexpr uint bitsize = 0;
|
||||
|
||||
static constexpr uint data_mask()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto extract(const T& data) -> decltype(+T())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static constexpr T insert(T value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Fixed field (provides constant values in field pack)
|
||||
template<typename T, T V, uint N>
|
||||
struct ff_t : bf_base<T, N>
|
||||
{
|
||||
using type = typename ff_t::type;
|
||||
using vtype = typename ff_t::vtype;
|
||||
|
||||
// Return constant value
|
||||
static constexpr vtype extract(const type& data)
|
||||
{
|
||||
static_assert((V & ff_t::vmask) == V, "ff_t<> error: V out of bounds");
|
||||
return V;
|
||||
}
|
||||
|
||||
// Get value
|
||||
operator vtype() const
|
||||
{
|
||||
return V;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, uint I, uint N>
|
||||
struct fmt_unveil<bf_t<T, I, N>, void>
|
||||
{
|
||||
using type = typename fmt_unveil<simple_t<T>>::type;
|
||||
|
||||
static inline auto get(const bf_t<T, I, N>& bf)
|
||||
{
|
||||
return fmt_unveil<type>::get(bf);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F, typename... Fields>
|
||||
struct fmt_unveil<cf_t<F, Fields...>, void>
|
||||
{
|
||||
using type = typename fmt_unveil<simple_t<typename F::type>>::type;
|
||||
|
||||
static inline auto get(const cf_t<F, Fields...>& cf)
|
||||
{
|
||||
return fmt_unveil<type>::get(cf);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, T V, uint N>
|
||||
struct fmt_unveil<ff_t<T, V, N>, void>
|
||||
{
|
||||
using type = typename fmt_unveil<simple_t<T>>::type;
|
||||
|
||||
static inline auto get(const ff_t<T, V, N>& ff)
|
||||
{
|
||||
return fmt_unveil<type>::get(ff);
|
||||
}
|
||||
};
|
||||
template<typename T, u32 I, u32 N> using bf_le_t = bf_t<le_t<T>, I, N>;
|
||||
|
||||
-1702
File diff suppressed because it is too large
Load Diff
@@ -1,343 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include "yaml-cpp/yaml.h"
|
||||
|
||||
#include <typeinfo>
|
||||
|
||||
namespace cfg
|
||||
{
|
||||
logs::channel cfg("CFG");
|
||||
|
||||
_base::_base(type _type)
|
||||
: m_type(_type)
|
||||
{
|
||||
if (_type != type::node)
|
||||
{
|
||||
fmt::throw_exception<std::logic_error>("Invalid root node" HERE);
|
||||
}
|
||||
}
|
||||
|
||||
_base::_base(type _type, node* owner, const std::string& name)
|
||||
: m_type(_type)
|
||||
{
|
||||
for (const auto& pair : owner->m_nodes)
|
||||
{
|
||||
if (pair.first == name)
|
||||
{
|
||||
fmt::throw_exception<std::logic_error>("Node already exists: %s" HERE, name);
|
||||
}
|
||||
}
|
||||
|
||||
owner->m_nodes.emplace_back(name, this);
|
||||
}
|
||||
|
||||
bool _base::from_string(const std::string&)
|
||||
{
|
||||
fmt::throw_exception<std::logic_error>("from_string() purecall" HERE);
|
||||
}
|
||||
|
||||
bool _base::from_list(std::vector<std::string>&&)
|
||||
{
|
||||
fmt::throw_exception<std::logic_error>("from_list() purecall" HERE);
|
||||
}
|
||||
|
||||
// Emit YAML
|
||||
static void encode(YAML::Emitter& out, const class _base& rhs);
|
||||
|
||||
// Incrementally load config entries from YAML::Node.
|
||||
// The config value is preserved if the corresponding YAML node doesn't exist.
|
||||
static void decode(const YAML::Node& data, class _base& rhs);
|
||||
}
|
||||
|
||||
std::vector<std::string> cfg::make_int_range(s64 min, s64 max)
|
||||
{
|
||||
return {std::to_string(min), std::to_string(max)};
|
||||
}
|
||||
|
||||
bool cfg::try_to_int64(s64* out, const std::string& value, s64 min, s64 max)
|
||||
{
|
||||
// TODO: this could be rewritten without exceptions (but it should be as safe as possible and provide logs)
|
||||
s64 result;
|
||||
std::size_t pos;
|
||||
|
||||
try
|
||||
{
|
||||
result = std::stoll(value, &pos, 0 /* Auto-detect numeric base */);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
if (out) cfg.error("cfg::try_to_int('%s'): exception: %s", value, e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pos != value.size())
|
||||
{
|
||||
if (out) cfg.error("cfg::try_to_int('%s'): unexpected characters (pos=%zu)", value, pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result < min || result > max)
|
||||
{
|
||||
if (out) cfg.error("cfg::try_to_int('%s'): out of bounds (%lld..%lld)", value, min, max);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out) *out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, const std::string& value)
|
||||
{
|
||||
u64 max = -1;
|
||||
|
||||
for (u64 i = 0;; i++)
|
||||
{
|
||||
std::string var;
|
||||
func(var, i);
|
||||
|
||||
if (var == value)
|
||||
{
|
||||
if (out) *out = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string hex;
|
||||
fmt_class_string<u64>::format(hex, i);
|
||||
if (var == hex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
max = i;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::size_t pos;
|
||||
const auto val = std::stoull(value, &pos, 0);
|
||||
|
||||
if (pos != value.size())
|
||||
{
|
||||
if (out) cfg.error("cfg::try_to_enum_value('%s'): unexpected characters (pos=%zu)", value, pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (val > max)
|
||||
{
|
||||
if (out) cfg.error("cfg::try_to_enum_value('%s'): out of bounds(0..%u)", value, max);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out) *out = val;
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
if (out) cfg.error("cfg::try_to_enum_value('%s'): invalid enum value: %s", value, e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> cfg::try_to_enum_list(decltype(&fmt_class_string<int>::format) func)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
for (u64 i = 0;; i++)
|
||||
{
|
||||
std::string var;
|
||||
func(var, i);
|
||||
|
||||
std::string hex;
|
||||
fmt_class_string<u64>::format(hex, i);
|
||||
if (var == hex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(var));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void cfg::encode(YAML::Emitter& out, const cfg::_base& rhs)
|
||||
{
|
||||
switch (rhs.get_type())
|
||||
{
|
||||
case type::node:
|
||||
{
|
||||
out << YAML::BeginMap;
|
||||
for (const auto& np : static_cast<const node&>(rhs).get_nodes())
|
||||
{
|
||||
out << YAML::Key << np.first;
|
||||
out << YAML::Value;
|
||||
encode(out, *np.second);
|
||||
}
|
||||
|
||||
out << YAML::EndMap;
|
||||
return;
|
||||
}
|
||||
case type::set:
|
||||
{
|
||||
out << YAML::BeginSeq;
|
||||
for (const auto& str : static_cast<const set_entry&>(rhs).get_set())
|
||||
{
|
||||
out << str;
|
||||
}
|
||||
|
||||
out << YAML::EndSeq;
|
||||
return;
|
||||
}
|
||||
case type::log:
|
||||
{
|
||||
out << YAML::BeginMap;
|
||||
for (const auto& np : static_cast<const log_entry&>(rhs).get_map())
|
||||
{
|
||||
if (np.second == logs::level::notice) continue;
|
||||
out << YAML::Key << np.first;
|
||||
out << YAML::Value << fmt::format("%s", np.second);
|
||||
}
|
||||
|
||||
out << YAML::EndMap;
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
out << rhs.to_string();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cfg::decode(const YAML::Node& data, cfg::_base& rhs)
|
||||
{
|
||||
switch (rhs.get_type())
|
||||
{
|
||||
case type::node:
|
||||
{
|
||||
if (data.IsScalar() || data.IsSequence())
|
||||
{
|
||||
return; // ???
|
||||
}
|
||||
|
||||
for (const auto& pair : data)
|
||||
{
|
||||
if (!pair.first.IsScalar()) continue;
|
||||
|
||||
// Find the key among existing nodes
|
||||
for (const auto& _pair : static_cast<node&>(rhs).get_nodes())
|
||||
{
|
||||
if (_pair.first == pair.first.Scalar())
|
||||
{
|
||||
decode(pair.second, *_pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case type::set:
|
||||
{
|
||||
std::vector<std::string> values;
|
||||
|
||||
if (YAML::convert<decltype(values)>::decode(data, values))
|
||||
{
|
||||
rhs.from_list(std::move(values));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case type::log:
|
||||
{
|
||||
if (data.IsScalar() || data.IsSequence())
|
||||
{
|
||||
return; // ???
|
||||
}
|
||||
|
||||
std::map<std::string, logs::level> values;
|
||||
|
||||
for (const auto& pair : data)
|
||||
{
|
||||
if (!pair.first.IsScalar() || !pair.second.IsScalar()) continue;
|
||||
|
||||
u64 value;
|
||||
if (cfg::try_to_enum_value(&value, &fmt_class_string<logs::level>::format, pair.second.Scalar()))
|
||||
{
|
||||
values.emplace(pair.first.Scalar(), static_cast<logs::level>(static_cast<int>(value)));
|
||||
}
|
||||
}
|
||||
|
||||
static_cast<log_entry&>(rhs).set_map(std::move(values));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
std::string value;
|
||||
|
||||
if (YAML::convert<std::string>::decode(data, value))
|
||||
{
|
||||
rhs.from_string(value);
|
||||
}
|
||||
|
||||
break; // ???
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string cfg::node::to_string() const
|
||||
{
|
||||
YAML::Emitter out;
|
||||
cfg::encode(out, *this);
|
||||
|
||||
return {out.c_str(), out.size()};
|
||||
}
|
||||
|
||||
bool cfg::node::from_string(const std::string& value) try
|
||||
{
|
||||
cfg::decode(YAML::Load(value), *this);
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cfg.fatal("%s thrown: %s", typeid(e).name(), e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
void cfg::node::from_default()
|
||||
{
|
||||
for (auto& node : m_nodes)
|
||||
{
|
||||
node.second->from_default();
|
||||
}
|
||||
}
|
||||
|
||||
void cfg::_bool::from_default()
|
||||
{
|
||||
m_value = def;
|
||||
}
|
||||
|
||||
void cfg::string::from_default()
|
||||
{
|
||||
m_value = def;
|
||||
}
|
||||
|
||||
void cfg::set_entry::from_default()
|
||||
{
|
||||
m_set = {};
|
||||
}
|
||||
|
||||
void cfg::log_entry::set_map(std::map<std::string, logs::level>&& map)
|
||||
{
|
||||
logs::reset();
|
||||
|
||||
for (auto&& pair : (m_map = std::move(map)))
|
||||
{
|
||||
logs::set_level(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void cfg::log_entry::from_default()
|
||||
{
|
||||
set_map({});
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/types.h"
|
||||
#include "Utilities/StrFmt.h"
|
||||
#include "Utilities/Log.h"
|
||||
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace cfg
|
||||
{
|
||||
// Format min and max values
|
||||
std::vector<std::string> make_int_range(s64 min, s64 max);
|
||||
|
||||
// Convert string to signed integer
|
||||
bool try_to_int64(s64* out, const std::string& value, s64 min, s64 max);
|
||||
|
||||
// Internal hack
|
||||
bool try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, const std::string&);
|
||||
|
||||
// Internal hack
|
||||
std::vector<std::string> try_to_enum_list(decltype(&fmt_class_string<int>::format) func);
|
||||
|
||||
// Config tree entry type.
|
||||
enum class type : uint
|
||||
{
|
||||
node = 0, // cfg::node type
|
||||
_bool, // cfg::_bool type
|
||||
_enum, // cfg::_enum type
|
||||
_int, // cfg::_int type
|
||||
string, // cfg::string type
|
||||
set, // cfg::set_entry type
|
||||
log,
|
||||
};
|
||||
|
||||
// Config tree entry abstract base class
|
||||
class _base
|
||||
{
|
||||
const type m_type;
|
||||
|
||||
protected:
|
||||
// Ownerless entry constructor
|
||||
_base(type _type);
|
||||
|
||||
// Owned entry constructor
|
||||
_base(type _type, class node* owner, const std::string& name);
|
||||
|
||||
public:
|
||||
// Disallow copy/move constructors and assignments
|
||||
_base(const _base&) = delete;
|
||||
|
||||
// Get type
|
||||
type get_type() const { return m_type; }
|
||||
|
||||
// Reset defaults
|
||||
virtual void from_default() = 0;
|
||||
|
||||
// Convert to string (optional)
|
||||
virtual std::string to_string() const
|
||||
{
|
||||
return{};
|
||||
}
|
||||
|
||||
// Try to convert from string (optional)
|
||||
virtual bool from_string(const std::string&);
|
||||
|
||||
// Get string list (optional)
|
||||
virtual std::vector<std::string> to_list() const
|
||||
{
|
||||
return{};
|
||||
}
|
||||
|
||||
// Set multiple values. Implementation-specific, optional.
|
||||
virtual bool from_list(std::vector<std::string>&&);
|
||||
};
|
||||
|
||||
// Config tree node which contains another nodes
|
||||
class node : public _base
|
||||
{
|
||||
std::vector<std::pair<std::string, _base*>> m_nodes;
|
||||
|
||||
friend class _base;
|
||||
|
||||
public:
|
||||
// Root node constructor
|
||||
node()
|
||||
: _base(type::node)
|
||||
{
|
||||
}
|
||||
|
||||
// Registered node constructor
|
||||
node(node* owner, const std::string& name)
|
||||
: _base(type::node, owner, name)
|
||||
{
|
||||
}
|
||||
|
||||
// Get child nodes
|
||||
const auto& get_nodes() const
|
||||
{
|
||||
return m_nodes;
|
||||
}
|
||||
|
||||
// Serialize node
|
||||
std::string to_string() const override;
|
||||
|
||||
// Deserialize node
|
||||
bool from_string(const std::string& value) override;
|
||||
|
||||
// Set default values
|
||||
void from_default() override;
|
||||
};
|
||||
|
||||
class _bool final : public _base
|
||||
{
|
||||
bool m_value;
|
||||
|
||||
public:
|
||||
bool def;
|
||||
|
||||
_bool(node* owner, const std::string& name, bool def = false)
|
||||
: _base(type::_bool, owner, name)
|
||||
, m_value(def)
|
||||
, def(def)
|
||||
{
|
||||
}
|
||||
|
||||
explicit operator bool() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void from_default() override;
|
||||
|
||||
std::string to_string() const override
|
||||
{
|
||||
return m_value ? "true" : "false";
|
||||
}
|
||||
|
||||
bool from_string(const std::string& value) override
|
||||
{
|
||||
if (value == "false")
|
||||
m_value = false;
|
||||
else if (value == "true")
|
||||
m_value = true;
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void set(const bool& value)
|
||||
{
|
||||
m_value = value;
|
||||
}
|
||||
};
|
||||
|
||||
// Value node with fixed set of possible values, each maps to an enum value of type T.
|
||||
template <typename T>
|
||||
class _enum final : public _base
|
||||
{
|
||||
T m_value;
|
||||
|
||||
public:
|
||||
const T def;
|
||||
|
||||
_enum(node* owner, const std::string& name, T value = {})
|
||||
: _base(type::_enum, owner, name)
|
||||
, m_value(value)
|
||||
, def(value)
|
||||
{
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void from_default() override
|
||||
{
|
||||
m_value = def;
|
||||
}
|
||||
|
||||
std::string to_string() const override
|
||||
{
|
||||
std::string result;
|
||||
fmt_class_string<T>::format(result, fmt_unveil<T>::get(m_value));
|
||||
return result; // TODO: ???
|
||||
}
|
||||
|
||||
bool from_string(const std::string& value) override
|
||||
{
|
||||
u64 result;
|
||||
|
||||
if (try_to_enum_value(&result, &fmt_class_string<T>::format, value))
|
||||
{
|
||||
// No narrowing check, it's hard to do right there
|
||||
m_value = static_cast<T>(static_cast<std::underlying_type_t<T>>(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> to_list() const override
|
||||
{
|
||||
return try_to_enum_list(&fmt_class_string<T>::format);
|
||||
}
|
||||
};
|
||||
|
||||
// Signed 32/64-bit integer entry with custom Min/Max range.
|
||||
template <s64 Min, s64 Max>
|
||||
class _int final : public _base
|
||||
{
|
||||
static_assert(Min < Max, "Invalid cfg::_int range");
|
||||
|
||||
// Prefer 32 bit type if possible
|
||||
using int_type = std::conditional_t<Min >= INT32_MIN && Max <= INT32_MAX, s32, s64>;
|
||||
|
||||
int_type m_value;
|
||||
|
||||
public:
|
||||
int_type def;
|
||||
|
||||
_int(node* owner, const std::string& name, int_type def = std::min<int_type>(Max, std::max<int_type>(Min, 0)))
|
||||
: _base(type::_int, owner, name)
|
||||
, m_value(def)
|
||||
, def(def)
|
||||
{
|
||||
}
|
||||
|
||||
operator int_type() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void from_default() override
|
||||
{
|
||||
m_value = def;
|
||||
}
|
||||
|
||||
std::string to_string() const override
|
||||
{
|
||||
return std::to_string(m_value);
|
||||
}
|
||||
|
||||
bool from_string(const std::string& value) override
|
||||
{
|
||||
s64 result;
|
||||
if (try_to_int64(&result, value, Min, Max))
|
||||
{
|
||||
m_value = static_cast<int_type>(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void set(const s64& value)
|
||||
{
|
||||
m_value = static_cast<int_type>(value);
|
||||
}
|
||||
|
||||
std::vector<std::string> to_list() const override
|
||||
{
|
||||
return make_int_range(Min, Max);
|
||||
}
|
||||
};
|
||||
|
||||
// Alias for 32 bit int
|
||||
using int32 = _int<INT32_MIN, INT32_MAX>;
|
||||
|
||||
// Alias for 64 bit int
|
||||
using int64 = _int<INT64_MIN, INT64_MAX>;
|
||||
|
||||
// Simple string entry with mutex
|
||||
class string final : public _base
|
||||
{
|
||||
std::string m_value;
|
||||
|
||||
public:
|
||||
std::string def;
|
||||
|
||||
string(node* owner, const std::string& name, const std::string& def = {})
|
||||
: _base(type::string, owner, name)
|
||||
, m_value(def)
|
||||
, def(def)
|
||||
{
|
||||
}
|
||||
|
||||
operator std::string() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
const std::string& get() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_value.size();
|
||||
}
|
||||
|
||||
void from_default() override;
|
||||
|
||||
std::string to_string() const override
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
bool from_string(const std::string& value) override
|
||||
{
|
||||
m_value = value;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Simple set entry with mutex (TODO: template for various types)
|
||||
class set_entry final : public _base
|
||||
{
|
||||
std::set<std::string> m_set;
|
||||
|
||||
public:
|
||||
// Default value is empty list in current implementation
|
||||
set_entry(node* owner, const std::string& name)
|
||||
: _base(type::set, owner, name)
|
||||
{
|
||||
}
|
||||
|
||||
std::set<std::string> get_set() const
|
||||
{
|
||||
return m_set;
|
||||
}
|
||||
|
||||
void set_set(std::set<std::string>&& set)
|
||||
{
|
||||
m_set = std::move(set);
|
||||
}
|
||||
|
||||
void from_default() override;
|
||||
|
||||
std::vector<std::string> to_list() const override
|
||||
{
|
||||
return{ m_set.begin(), m_set.end() };
|
||||
}
|
||||
|
||||
bool from_list(std::vector<std::string>&& list) override
|
||||
{
|
||||
m_set = { std::make_move_iterator(list.begin()), std::make_move_iterator(list.end()) };
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class log_entry final : public _base
|
||||
{
|
||||
std::map<std::string, logs::level> m_map;
|
||||
|
||||
public:
|
||||
log_entry(node* owner, const std::string& name)
|
||||
: _base(type::log, owner, name)
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, logs::level> get_map() const
|
||||
{
|
||||
return m_map;
|
||||
}
|
||||
|
||||
void set_map(std::map<std::string, logs::level>&& map);
|
||||
|
||||
void from_default() override;
|
||||
};
|
||||
}
|
||||
+657
-1090
File diff suppressed because it is too large
Load Diff
+208
-403
@@ -1,58 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "bit_set.h"
|
||||
namespace fom // file open mode
|
||||
{
|
||||
enum open_mode : u32
|
||||
{
|
||||
read = 1 << 0, // enable reading
|
||||
write = 1 << 1, // enable writing
|
||||
append = 1 << 2, // enable appending (always write to the end of file)
|
||||
create = 1 << 3, // create file if it doesn't exist
|
||||
trunc = 1 << 4, // clear opened file if it's not empty
|
||||
excl = 1 << 5, // failure if the file already exists (used with `create`)
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
rewrite = write | create | trunc,
|
||||
};
|
||||
};
|
||||
|
||||
namespace fs
|
||||
{
|
||||
#ifdef _WIN32
|
||||
using native_handle = void*;
|
||||
#else
|
||||
using native_handle = int;
|
||||
#endif
|
||||
|
||||
// File open mode flags
|
||||
enum class open_mode : u32
|
||||
{
|
||||
read,
|
||||
write,
|
||||
append,
|
||||
create,
|
||||
trunc,
|
||||
excl,
|
||||
unshare,
|
||||
|
||||
__bitset_enum_max
|
||||
};
|
||||
|
||||
constexpr auto read = +open_mode::read; // Enable reading
|
||||
constexpr auto write = +open_mode::write; // Enable writing
|
||||
constexpr auto append = +open_mode::append; // Always append to the end of the file
|
||||
constexpr auto create = +open_mode::create; // Create file if it doesn't exist
|
||||
constexpr auto trunc = +open_mode::trunc; // Clear opened file if it's not empty
|
||||
constexpr auto excl = +open_mode::excl; // Failure if the file already exists (used with `create`)
|
||||
constexpr auto unshare = +open_mode::unshare; // Prevent opening the file twice
|
||||
|
||||
constexpr auto rewrite = open_mode::write + open_mode::create + open_mode::trunc;
|
||||
|
||||
// File seek mode
|
||||
enum class seek_mode : u32
|
||||
enum seek_mode : u32 // file seek mode
|
||||
{
|
||||
seek_set,
|
||||
seek_cur,
|
||||
seek_end,
|
||||
};
|
||||
|
||||
constexpr auto seek_set = seek_mode::seek_set; // From beginning
|
||||
constexpr auto seek_cur = seek_mode::seek_cur; // From current position
|
||||
constexpr auto seek_end = seek_mode::seek_end; // From end
|
||||
|
||||
// File attributes (TODO)
|
||||
struct stat_t
|
||||
{
|
||||
bool is_directory;
|
||||
@@ -63,75 +34,7 @@ namespace fs
|
||||
s64 ctime;
|
||||
};
|
||||
|
||||
// Native handle getter
|
||||
struct get_native_handle
|
||||
{
|
||||
virtual native_handle get() = 0;
|
||||
};
|
||||
|
||||
// File handle base
|
||||
struct file_base
|
||||
{
|
||||
virtual ~file_base();
|
||||
|
||||
virtual stat_t stat();
|
||||
virtual void sync();
|
||||
virtual bool trunc(u64 length) = 0;
|
||||
virtual u64 read(void* buffer, u64 size) = 0;
|
||||
virtual u64 write(const void* buffer, u64 size) = 0;
|
||||
virtual u64 seek(s64 offset, seek_mode whence) = 0;
|
||||
virtual u64 size() = 0;
|
||||
};
|
||||
|
||||
// Directory entry (TODO)
|
||||
struct dir_entry : stat_t
|
||||
{
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// Directory handle base
|
||||
struct dir_base
|
||||
{
|
||||
virtual ~dir_base();
|
||||
|
||||
virtual bool read(dir_entry&) = 0;
|
||||
virtual void rewind() = 0;
|
||||
};
|
||||
|
||||
// Device information
|
||||
struct device_stat
|
||||
{
|
||||
u64 block_size;
|
||||
u64 total_size;
|
||||
u64 total_free; // Total size of free space
|
||||
u64 avail_free; // Free space available to unprivileged user
|
||||
};
|
||||
|
||||
// Virtual device
|
||||
struct device_base
|
||||
{
|
||||
virtual ~device_base();
|
||||
|
||||
virtual bool stat(const std::string& path, stat_t& info) = 0;
|
||||
virtual bool statfs(const std::string& path, device_stat& info) = 0;
|
||||
virtual bool remove_dir(const std::string& path) = 0;
|
||||
virtual bool create_dir(const std::string& path) = 0;
|
||||
virtual bool rename(const std::string& from, const std::string& to) = 0;
|
||||
virtual bool remove(const std::string& path) = 0;
|
||||
virtual bool trunc(const std::string& path, u64 length) = 0;
|
||||
virtual bool utime(const std::string& path, s64 atime, s64 mtime) = 0;
|
||||
|
||||
virtual std::unique_ptr<file_base> open(const std::string& path, bs_t<open_mode> mode) = 0;
|
||||
virtual std::unique_ptr<dir_base> open_dir(const std::string& path) = 0;
|
||||
};
|
||||
|
||||
// Get virtual device for specified path (nullptr for real path)
|
||||
std::shared_ptr<device_base> get_virtual_device(const std::string& path);
|
||||
|
||||
// Set virtual device with specified name (nullptr for deletion)
|
||||
std::shared_ptr<device_base> set_virtual_device(const std::string& root_name, const std::shared_ptr<device_base>&);
|
||||
|
||||
// Try to get parent directory (returns empty string on failure)
|
||||
// Get parent directory for the path (returns empty string on failure)
|
||||
std::string get_parent_dir(const std::string& path);
|
||||
|
||||
// Get file information
|
||||
@@ -146,9 +49,6 @@ namespace fs
|
||||
// Check whether the directory exists and is NOT a file
|
||||
bool is_dir(const std::string& path);
|
||||
|
||||
// Get filesystem information
|
||||
bool statfs(const std::string& path, device_stat& info);
|
||||
|
||||
// Delete empty directory
|
||||
bool remove_dir(const std::string& path);
|
||||
|
||||
@@ -159,7 +59,7 @@ namespace fs
|
||||
bool create_path(const std::string& path);
|
||||
|
||||
// Rename (move) file or directory
|
||||
bool rename(const std::string& from, const std::string& to, bool overwrite);
|
||||
bool rename(const std::string& from, const std::string& to);
|
||||
|
||||
// Copy file contents
|
||||
bool copy_file(const std::string& from, const std::string& to, bool overwrite);
|
||||
@@ -170,116 +70,79 @@ namespace fs
|
||||
// Change file size (possibly appending zeros)
|
||||
bool truncate_file(const std::string& path, u64 length);
|
||||
|
||||
// Set file access/modification time
|
||||
bool utime(const std::string& path, s64 atime, s64 mtime);
|
||||
|
||||
class file final
|
||||
{
|
||||
std::unique_ptr<file_base> m_file;
|
||||
using handle_type = std::intptr_t;
|
||||
|
||||
[[noreturn]] void xnull() const;
|
||||
[[noreturn]] void xfail() const;
|
||||
constexpr static handle_type null = -1;
|
||||
|
||||
handle_type m_fd = null;
|
||||
|
||||
friend class file_read_map;
|
||||
friend class file_write_map;
|
||||
|
||||
public:
|
||||
// Default constructor
|
||||
file() = default;
|
||||
|
||||
// Open file with specified mode
|
||||
explicit file(const std::string& path, bs_t<open_mode> mode = ::fs::read);
|
||||
|
||||
// Open memory for read
|
||||
explicit file(const void* ptr, std::size_t size);
|
||||
|
||||
// Open file with specified args (forward to constructor)
|
||||
template <typename... Args>
|
||||
bool open(Args&&... args)
|
||||
explicit file(const std::string& path, u32 mode = fom::read)
|
||||
{
|
||||
*this = fs::file(std::forward<Args>(args)...);
|
||||
return m_file.operator bool();
|
||||
open(path, mode);
|
||||
}
|
||||
|
||||
file(file&& other)
|
||||
: m_fd(other.m_fd)
|
||||
{
|
||||
other.m_fd = null;
|
||||
}
|
||||
|
||||
file& operator =(file&& right)
|
||||
{
|
||||
std::swap(m_fd, right.m_fd);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~file();
|
||||
|
||||
// Check whether the handle is valid (opened file)
|
||||
bool is_opened() const
|
||||
{
|
||||
return m_fd != null;
|
||||
}
|
||||
|
||||
// Check whether the handle is valid (opened file)
|
||||
explicit operator bool() const
|
||||
{
|
||||
return m_file.operator bool();
|
||||
return is_opened();
|
||||
}
|
||||
|
||||
// Close the file explicitly
|
||||
void close()
|
||||
{
|
||||
m_file.reset();
|
||||
}
|
||||
|
||||
void reset(std::unique_ptr<file_base>&& ptr)
|
||||
{
|
||||
m_file = std::move(ptr);
|
||||
}
|
||||
|
||||
std::unique_ptr<file_base> release()
|
||||
{
|
||||
return std::move(m_file);
|
||||
}
|
||||
// Open specified file with specified mode
|
||||
bool open(const std::string& path, u32 mode = fom::read);
|
||||
|
||||
// Change file size (possibly appending zero bytes)
|
||||
bool trunc(u64 length) const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->trunc(length);
|
||||
}
|
||||
bool trunc(u64 size) const;
|
||||
|
||||
// Get file information
|
||||
stat_t stat() const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->stat();
|
||||
}
|
||||
bool stat(stat_t& info) const;
|
||||
|
||||
// Sync file buffers
|
||||
void sync() const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->sync();
|
||||
}
|
||||
// Close the file explicitly (destructor automatically closes the file)
|
||||
void close();
|
||||
|
||||
// Read the data from the file and return the amount of data written in buffer
|
||||
u64 read(void* buffer, u64 count) const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->read(buffer, count);
|
||||
}
|
||||
u64 read(void* buffer, u64 count) const;
|
||||
|
||||
// Write the data to the file and return the amount of data actually written
|
||||
u64 write(const void* buffer, u64 count) const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->write(buffer, count);
|
||||
}
|
||||
u64 write(const void* buffer, u64 count) const;
|
||||
|
||||
// Change current position, returns resulting position
|
||||
u64 seek(s64 offset, seek_mode whence = seek_set) const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->seek(offset, whence);
|
||||
}
|
||||
// Move file pointer
|
||||
u64 seek(s64 offset, seek_mode whence = seek_set) const;
|
||||
|
||||
// Get file size
|
||||
u64 size() const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->size();
|
||||
}
|
||||
|
||||
// Get current position
|
||||
u64 pos() const
|
||||
{
|
||||
if (!m_file) xnull();
|
||||
return m_file->seek(0, seek_cur);
|
||||
}
|
||||
u64 size() const;
|
||||
|
||||
// Write std::string unconditionally
|
||||
const file& write(const std::string& str) const
|
||||
{
|
||||
if (write(str.data(), str.size()) != str.size()) xfail();
|
||||
CHECK_ASSERTION(write(str.data(), str.size()) == str.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -287,7 +150,7 @@ namespace fs
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_pod<T>::value && !std::is_pointer<T>::value, const file&> write(const T& data) const
|
||||
{
|
||||
if (write(std::addressof(data), sizeof(T)) != sizeof(T)) xfail();
|
||||
CHECK_ASSERTION(write(std::addressof(data), sizeof(T)) == sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -295,7 +158,7 @@ namespace fs
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_pod<T>::value && !std::is_pointer<T>::value, const file&> write(const std::vector<T>& vec) const
|
||||
{
|
||||
if (write(vec.data(), vec.size() * sizeof(T)) != vec.size() * sizeof(T)) xfail();
|
||||
CHECK_ASSERTION(write(vec.data(), vec.size() * sizeof(T)) == vec.size() * sizeof(T));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -305,13 +168,6 @@ namespace fs
|
||||
return read(&str[0], str.size()) == str.size();
|
||||
}
|
||||
|
||||
// Read std::string
|
||||
bool read(std::string& str, std::size_t size) const
|
||||
{
|
||||
str.resize(size);
|
||||
return read(&str[0], size) == size;
|
||||
}
|
||||
|
||||
// Read POD, sizeof(T) is used
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_pod<T>::value && !std::is_pointer<T>::value, bool> read(T& data) const
|
||||
@@ -326,20 +182,12 @@ namespace fs
|
||||
return read(vec.data(), sizeof(T) * vec.size()) == sizeof(T) * vec.size();
|
||||
}
|
||||
|
||||
// Read POD std::vector
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_pod<T>::value && !std::is_pointer<T>::value, bool> read(std::vector<T>& vec, std::size_t size) const
|
||||
{
|
||||
vec.resize(size);
|
||||
return read(vec.data(), sizeof(T) * size) == sizeof(T) * size;
|
||||
}
|
||||
|
||||
// Read POD (experimental)
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_pod<T>::value && !std::is_pointer<T>::value, T> read() const
|
||||
{
|
||||
T result;
|
||||
if (!read(result)) xfail();
|
||||
CHECK_ASSERTION(read(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -348,7 +196,7 @@ namespace fs
|
||||
{
|
||||
std::string result;
|
||||
result.resize(size());
|
||||
if (seek(0), !read(result)) xfail();
|
||||
CHECK_ASSERTION(seek(0) != -1 && read(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -358,72 +206,164 @@ namespace fs
|
||||
{
|
||||
std::vector<T> result;
|
||||
result.resize(size() / sizeof(T));
|
||||
if (seek(0), !read(result)) xfail();
|
||||
CHECK_ASSERTION(seek(0) != -1 && read(result));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Get native handle if available
|
||||
native_handle get_handle() const;
|
||||
// TODO
|
||||
class file_read_map final
|
||||
{
|
||||
char* m_ptr = nullptr;
|
||||
u64 m_size;
|
||||
|
||||
public:
|
||||
file_read_map() = default;
|
||||
|
||||
file_read_map(file_read_map&& right)
|
||||
: m_ptr(right.m_ptr)
|
||||
, m_size(right.m_size)
|
||||
{
|
||||
right.m_ptr = 0;
|
||||
}
|
||||
|
||||
file_read_map& operator =(file_read_map&& right)
|
||||
{
|
||||
std::swap(m_ptr, right.m_ptr);
|
||||
std::swap(m_size, right.m_size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
file_read_map(const file& f)
|
||||
{
|
||||
reset(f);
|
||||
}
|
||||
|
||||
~file_read_map()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
// Open file mapping
|
||||
void reset(const file& f);
|
||||
|
||||
// Close file mapping
|
||||
void reset();
|
||||
|
||||
// Get pointer
|
||||
operator const char*() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO
|
||||
class file_write_map final
|
||||
{
|
||||
char* m_ptr = nullptr;
|
||||
u64 m_size;
|
||||
|
||||
public:
|
||||
file_write_map() = default;
|
||||
|
||||
file_write_map(file_write_map&& right)
|
||||
: m_ptr(right.m_ptr)
|
||||
, m_size(right.m_size)
|
||||
{
|
||||
right.m_ptr = 0;
|
||||
}
|
||||
|
||||
file_write_map& operator =(file_write_map&& right)
|
||||
{
|
||||
std::swap(m_ptr, right.m_ptr);
|
||||
std::swap(m_size, right.m_size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
file_write_map(const file& f)
|
||||
{
|
||||
reset(f);
|
||||
}
|
||||
|
||||
~file_write_map()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
// Open file mapping
|
||||
void reset(const file& f);
|
||||
|
||||
// Close file mapping
|
||||
void reset();
|
||||
|
||||
// Get pointer
|
||||
operator char*() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
class dir final
|
||||
{
|
||||
std::unique_ptr<dir_base> m_dir;
|
||||
|
||||
[[noreturn]] void xnull() const;
|
||||
std::unique_ptr<char[]> m_path;
|
||||
std::intptr_t m_dd; // handle (aux)
|
||||
|
||||
public:
|
||||
dir() = default;
|
||||
|
||||
// Open dir handle
|
||||
explicit dir(const std::string& path)
|
||||
explicit dir(const std::string& dirname)
|
||||
{
|
||||
open(path);
|
||||
open(dirname);
|
||||
}
|
||||
|
||||
// Open specified directory
|
||||
bool open(const std::string& path);
|
||||
dir(dir&& other)
|
||||
: m_dd(other.m_dd)
|
||||
, m_path(std::move(other.m_path))
|
||||
{
|
||||
}
|
||||
|
||||
dir& operator =(dir&& right)
|
||||
{
|
||||
std::swap(m_dd, right.m_dd);
|
||||
std::swap(m_path, right.m_path);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~dir();
|
||||
|
||||
// Check whether the handle is valid (opened directory)
|
||||
bool is_opened() const
|
||||
{
|
||||
return m_path.operator bool();
|
||||
}
|
||||
|
||||
// Check whether the handle is valid (opened directory)
|
||||
explicit operator bool() const
|
||||
{
|
||||
return m_dir.operator bool();
|
||||
return is_opened();
|
||||
}
|
||||
|
||||
// Close the directory explicitly
|
||||
void close()
|
||||
{
|
||||
m_dir.reset();
|
||||
}
|
||||
|
||||
void reset(std::unique_ptr<dir_base>&& ptr)
|
||||
{
|
||||
m_dir = std::move(ptr);
|
||||
}
|
||||
// Open specified directory
|
||||
bool open(const std::string& dirname);
|
||||
|
||||
std::unique_ptr<dir_base> release()
|
||||
{
|
||||
return std::move(m_dir);
|
||||
}
|
||||
// Close the directory explicitly (destructor automatically closes the directory)
|
||||
void close();
|
||||
|
||||
// Get next directory entry
|
||||
bool read(dir_entry& out) const
|
||||
{
|
||||
if (!m_dir) xnull();
|
||||
return m_dir->read(out);
|
||||
}
|
||||
// Get next directory entry (UTF-8 name and file stat)
|
||||
bool read(std::string& name, stat_t& info);
|
||||
|
||||
// Reset to the beginning
|
||||
void rewind() const
|
||||
bool first(std::string& name, stat_t& info);
|
||||
|
||||
struct entry
|
||||
{
|
||||
if (!m_dir) xnull();
|
||||
return m_dir->rewind();
|
||||
}
|
||||
std::string name;
|
||||
stat_t info;
|
||||
};
|
||||
|
||||
class iterator
|
||||
{
|
||||
const dir* m_parent;
|
||||
dir_entry m_entry;
|
||||
entry m_entry;
|
||||
dir* m_parent;
|
||||
|
||||
public:
|
||||
enum class mode
|
||||
@@ -432,7 +372,7 @@ namespace fs
|
||||
from_current
|
||||
};
|
||||
|
||||
iterator(const dir* parent, mode mode_ = mode::from_first)
|
||||
iterator(dir* parent, mode mode_ = mode::from_first)
|
||||
: m_parent(parent)
|
||||
{
|
||||
if (!m_parent)
|
||||
@@ -442,23 +382,27 @@ namespace fs
|
||||
|
||||
if (mode_ == mode::from_first)
|
||||
{
|
||||
m_parent->rewind();
|
||||
m_parent->first(m_entry.name, m_entry.info);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_parent->read(m_entry.name, m_entry.info);
|
||||
}
|
||||
|
||||
if (!m_parent->read(m_entry))
|
||||
if (m_entry.name.empty())
|
||||
{
|
||||
m_parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
dir_entry& operator *()
|
||||
entry& operator *()
|
||||
{
|
||||
return m_entry;
|
||||
}
|
||||
|
||||
iterator& operator++()
|
||||
{
|
||||
*this = {m_parent, mode::from_current};
|
||||
*this = { m_parent, mode::from_current };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -468,159 +412,20 @@ namespace fs
|
||||
}
|
||||
};
|
||||
|
||||
iterator begin() const
|
||||
iterator begin()
|
||||
{
|
||||
return {m_dir ? this : nullptr};
|
||||
return{ this };
|
||||
}
|
||||
|
||||
iterator end() const
|
||||
iterator end()
|
||||
{
|
||||
return {nullptr};
|
||||
return{ nullptr };
|
||||
}
|
||||
};
|
||||
|
||||
// Get configuration directory
|
||||
const std::string& get_config_dir();
|
||||
|
||||
// Get data/cache directory for specified prefix and suffix
|
||||
std::string get_data_dir(const std::string& prefix, const std::string& location, const std::string& suffix);
|
||||
|
||||
// Get data/cache directory for specified prefix and path (suffix will be filename)
|
||||
std::string get_data_dir(const std::string& prefix, const std::string& path);
|
||||
|
||||
// Delete directory and all its contents recursively
|
||||
void remove_all(const std::string& path, bool remove_root = true);
|
||||
|
||||
// Get size of all files recursively
|
||||
u64 get_dir_size(const std::string& path);
|
||||
|
||||
enum class error : uint
|
||||
{
|
||||
ok = 0,
|
||||
|
||||
inval,
|
||||
noent,
|
||||
exist,
|
||||
acces,
|
||||
notempty,
|
||||
};
|
||||
|
||||
// Error code returned
|
||||
extern thread_local error g_tls_error;
|
||||
|
||||
template <typename T>
|
||||
struct container_stream final : file_base
|
||||
{
|
||||
// T can be a reference, but this is not recommended
|
||||
using value_type = typename std::remove_reference_t<T>::value_type;
|
||||
|
||||
T obj;
|
||||
u64 pos;
|
||||
|
||||
container_stream(T&& obj)
|
||||
: obj(std::forward<T>(obj))
|
||||
, pos(0)
|
||||
{
|
||||
}
|
||||
|
||||
~container_stream() override
|
||||
{
|
||||
}
|
||||
|
||||
bool trunc(u64 length) override
|
||||
{
|
||||
obj.resize(length);
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 read(void* buffer, u64 size) override
|
||||
{
|
||||
const u64 end = obj.size();
|
||||
|
||||
if (pos < end)
|
||||
{
|
||||
// Get readable size
|
||||
if (const u64 max = std::min<u64>(size, end - pos))
|
||||
{
|
||||
std::copy(obj.cbegin() + pos, obj.cbegin() + pos + max, static_cast<value_type*>(buffer));
|
||||
pos = pos + max;
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 write(const void* buffer, u64 size) override
|
||||
{
|
||||
const u64 old_size = obj.size();
|
||||
|
||||
if (old_size + size < old_size)
|
||||
{
|
||||
fmt::raw_error("fs::container_stream<>::write(): overflow");
|
||||
}
|
||||
|
||||
if (pos > old_size)
|
||||
{
|
||||
// Fill gap if necessary (default-initialized)
|
||||
obj.resize(pos);
|
||||
}
|
||||
|
||||
const auto src = static_cast<const value_type*>(buffer);
|
||||
|
||||
// Overwrite existing part
|
||||
const u64 overlap = std::min<u64>(obj.size() - pos, size);
|
||||
std::copy(src, src + overlap, obj.begin() + pos);
|
||||
|
||||
// Append new data
|
||||
obj.insert(obj.end(), src + overlap, src + size);
|
||||
pos += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
u64 seek(s64 offset, seek_mode whence) override
|
||||
{
|
||||
const s64 new_pos =
|
||||
whence == fs::seek_set ? offset :
|
||||
whence == fs::seek_cur ? offset + pos :
|
||||
whence == fs::seek_end ? offset + size() :
|
||||
(fmt::raw_error("fs::container_stream<>::seek(): invalid whence"), 0);
|
||||
|
||||
if (new_pos < 0)
|
||||
{
|
||||
fs::g_tls_error = fs::error::inval;
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = new_pos;
|
||||
return pos;
|
||||
}
|
||||
|
||||
u64 size() override
|
||||
{
|
||||
return obj.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
file make_stream(T&& container = T{})
|
||||
{
|
||||
file result;
|
||||
result.reset(std::make_unique<container_stream<T>>(std::forward<T>(container)));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool write_file(const std::string& path, bs_t<fs::open_mode> mode, const Args&... args)
|
||||
{
|
||||
if (fs::file f{path, mode})
|
||||
{
|
||||
// Write args sequentially
|
||||
int seq[]{ (f.write(args), 0)... };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// Get executable directory
|
||||
const std::string& get_executable_dir();
|
||||
}
|
||||
|
||||
@@ -1,817 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#ifdef WITH_GDB_DEBUGGER
|
||||
|
||||
#include "GDBDebugServer.h"
|
||||
#include "Log.h"
|
||||
#include <algorithm>
|
||||
#include "Emu/Memory/Memory.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/CPU/CPUThread.h"
|
||||
#include "Emu/Cell/PPUThread.h"
|
||||
#include "Emu/Cell/RawSPUThread.h"
|
||||
#include "Emu/Cell/SPUThread.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include"fcntl.h"
|
||||
#endif
|
||||
|
||||
extern void ppu_set_breakpoint(u32 addr);
|
||||
extern void ppu_remove_breakpoint(u32 addr);
|
||||
|
||||
logs::channel gdbDebugServer("gdbDebugServer");
|
||||
|
||||
int sock_init(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa_data;
|
||||
return WSAStartup(MAKEWORD(1, 1), &wsa_data);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int sock_quit(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return WSACleanup();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
int closesocket(socket_t s) {
|
||||
return close(s);
|
||||
}
|
||||
const int SOCKET_ERROR = -1;
|
||||
const socket_t INVALID_SOCKET = -1;
|
||||
#define sscanf_s sscanf
|
||||
#define HEX_U32 "x"
|
||||
#define HEX_U64 "lx"
|
||||
#else
|
||||
#define HEX_U32 "lx"
|
||||
#define HEX_U64 "llx"
|
||||
#endif
|
||||
|
||||
bool check_errno_again() {
|
||||
#ifdef _WIN32
|
||||
int err = GetLastError();
|
||||
return (err == WSAEWOULDBLOCK);
|
||||
#else
|
||||
int err = errno;
|
||||
return (err == EAGAIN) || (err == EWOULDBLOCK);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string u32_to_hex(u32 i) {
|
||||
return fmt::format("%" HEX_U32, i);
|
||||
}
|
||||
|
||||
std::string u64_to_padded_hex(u64 value) {
|
||||
return fmt::format("%.16" HEX_U64, value);
|
||||
}
|
||||
|
||||
std::string u32_to_padded_hex(u32 value) {
|
||||
return fmt::format("%.8" HEX_U32, value);
|
||||
}
|
||||
|
||||
u8 hex_to_u8(std::string val) {
|
||||
u8 result;
|
||||
sscanf_s(val.c_str(), "%02hhX", &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 hex_to_u32(std::string val) {
|
||||
u32 result;
|
||||
sscanf_s(val.c_str(), "%" HEX_U32, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
u64 hex_to_u64(std::string val) {
|
||||
u64 result;
|
||||
sscanf_s(val.c_str(), "%" HEX_U64, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void GDBDebugServer::start_server()
|
||||
{
|
||||
server_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (server_socket == INVALID_SOCKET) {
|
||||
gdbDebugServer.error("Error creating server socket");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
{
|
||||
int mode = 1;
|
||||
ioctlsocket(server_socket, FIONBIO, (u_long FAR *)&mode);
|
||||
}
|
||||
#else
|
||||
fcntl(server_socket, F_SETFL, fcntl(server_socket, F_GETFL) | O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
int err;
|
||||
|
||||
sockaddr_in server_saddr;
|
||||
server_saddr.sin_family = AF_INET;
|
||||
int port = g_cfg.misc.gdb_server_port;
|
||||
server_saddr.sin_port = htons(port);
|
||||
server_saddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
err = bind(server_socket, (struct sockaddr *) &server_saddr, sizeof(server_saddr));
|
||||
if (err == SOCKET_ERROR) {
|
||||
gdbDebugServer.error("Error binding to port %d", port);
|
||||
return;
|
||||
}
|
||||
|
||||
err = listen(server_socket, 1);
|
||||
if (err == SOCKET_ERROR) {
|
||||
gdbDebugServer.error("Error listening on port %d", port);
|
||||
return;
|
||||
}
|
||||
|
||||
gdbDebugServer.success("GDB Debug Server listening on port %d", port);
|
||||
}
|
||||
|
||||
int GDBDebugServer::read(void * buf, int cnt)
|
||||
{
|
||||
while (!stop) {
|
||||
int result = recv(client_socket, reinterpret_cast<char*>(buf), cnt, 0);
|
||||
|
||||
if (result == SOCKET_ERROR) {
|
||||
if (check_errno_again()) {
|
||||
thread_ctrl::wait_for(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
gdbDebugServer.error("Error during socket read");
|
||||
fmt::throw_exception("Error during socket read" HERE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char GDBDebugServer::read_char()
|
||||
{
|
||||
char result;
|
||||
int cnt = read(&result, 1);
|
||||
if (!cnt) {
|
||||
fmt::throw_exception("Tried to read char, but no data was available" HERE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
u8 GDBDebugServer::read_hexbyte()
|
||||
{
|
||||
char buf[2];
|
||||
read(buf, 2);
|
||||
return static_cast<u8>(strtol(buf, nullptr, 16));
|
||||
}
|
||||
|
||||
void GDBDebugServer::try_read_cmd(gdb_cmd & out_cmd)
|
||||
{
|
||||
char c = read_char();
|
||||
//interrupt
|
||||
if (UNLIKELY(c == 0x03)) {
|
||||
out_cmd.cmd = "\0x03";
|
||||
out_cmd.data = "";
|
||||
out_cmd.checksum = 0;
|
||||
return;
|
||||
}
|
||||
if (UNLIKELY(c != '$')) {
|
||||
//gdb starts conversation with + for some reason
|
||||
if (c == '+') {
|
||||
c = read_char();
|
||||
}
|
||||
if (c != '$') {
|
||||
fmt::throw_exception("Expected start of packet character '$', got '%c' instead" HERE, c);
|
||||
}
|
||||
}
|
||||
//clear packet data
|
||||
out_cmd.cmd = "";
|
||||
out_cmd.data = "";
|
||||
out_cmd.checksum = 0;
|
||||
bool cmd_part = true;
|
||||
u8 checksum = 0;
|
||||
while(true) {
|
||||
c = read_char();
|
||||
if (c == '#') {
|
||||
break;
|
||||
}
|
||||
checksum = (checksum + reinterpret_cast<u8&>(c)) % 256;
|
||||
//escaped char
|
||||
if (c == '}') {
|
||||
c = read_char() ^ 0x20;
|
||||
checksum = (checksum + reinterpret_cast<u8&>(c)) % 256;
|
||||
}
|
||||
//cmd-data splitters
|
||||
if (cmd_part && ((c == ':') || (c == '.') || (c == ';'))) {
|
||||
cmd_part = false;
|
||||
}
|
||||
if (cmd_part) {
|
||||
out_cmd.cmd += c;
|
||||
//only q and v commands can have multi-char command
|
||||
if ((out_cmd.cmd.length() == 1) && (c != 'q') && (c != 'v')) {
|
||||
cmd_part = false;
|
||||
}
|
||||
} else {
|
||||
out_cmd.data += c;
|
||||
}
|
||||
}
|
||||
out_cmd.checksum = read_hexbyte();
|
||||
if (out_cmd.checksum != checksum) {
|
||||
throw new wrong_checksum_exception("Wrong checksum for packet" HERE);
|
||||
}
|
||||
}
|
||||
|
||||
bool GDBDebugServer::read_cmd(gdb_cmd & out_cmd)
|
||||
{
|
||||
while (true) {
|
||||
try {
|
||||
try_read_cmd(out_cmd);
|
||||
ack(true);
|
||||
return true;
|
||||
}
|
||||
catch (wrong_checksum_exception) {
|
||||
ack(false);
|
||||
}
|
||||
catch (std::runtime_error e) {
|
||||
gdbDebugServer.error(e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDBDebugServer::send(const char * buf, int cnt)
|
||||
{
|
||||
gdbDebugServer.trace("Sending %s (%d bytes)", buf, cnt);
|
||||
while (!stop) {
|
||||
int res = ::send(client_socket, buf, cnt, 0);
|
||||
if (res == SOCKET_ERROR) {
|
||||
if (check_errno_again()) {
|
||||
thread_ctrl::wait_for(50);
|
||||
continue;
|
||||
}
|
||||
gdbDebugServer.error("Failed sending %d bytes", cnt);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GDBDebugServer::send_char(char c)
|
||||
{
|
||||
send(&c, 1);
|
||||
}
|
||||
|
||||
void GDBDebugServer::ack(bool accepted)
|
||||
{
|
||||
send_char(accepted ? '+' : '-');
|
||||
}
|
||||
|
||||
void GDBDebugServer::send_cmd(const std::string & cmd)
|
||||
{
|
||||
u8 checksum = 0;
|
||||
std::string buf;
|
||||
buf.reserve(cmd.length() + 4);
|
||||
buf += "$";
|
||||
for (int i = 0; i < cmd.length(); ++i) {
|
||||
checksum = (checksum + append_encoded_char(cmd[i], buf)) % 256;
|
||||
}
|
||||
buf += "#";
|
||||
buf += to_hexbyte(checksum);
|
||||
send(buf.c_str(), static_cast<int>(buf.length()));
|
||||
}
|
||||
|
||||
bool GDBDebugServer::send_cmd_ack(const std::string & cmd)
|
||||
{
|
||||
while (true) {
|
||||
send_cmd(cmd);
|
||||
char c = read_char();
|
||||
if (LIKELY(c == '+')) {
|
||||
return true;
|
||||
}
|
||||
if (UNLIKELY(c != '-')) {
|
||||
gdbDebugServer.error("Wrong acknowledge character received %c", c);
|
||||
return false;
|
||||
}
|
||||
gdbDebugServer.warning("Client rejected our cmd");
|
||||
}
|
||||
}
|
||||
|
||||
u8 GDBDebugServer::append_encoded_char(char c, std::string & str)
|
||||
{
|
||||
u8 checksum = 0;
|
||||
if (UNLIKELY((c == '#') || (c == '$') || (c == '}'))) {
|
||||
str += '}';
|
||||
c ^= 0x20;
|
||||
checksum = '}';
|
||||
}
|
||||
checksum = (checksum + reinterpret_cast<u8&>(c)) % 256;
|
||||
str += c;
|
||||
return checksum;
|
||||
}
|
||||
|
||||
std::string GDBDebugServer::to_hexbyte(u8 i)
|
||||
{
|
||||
std::string result = "00";
|
||||
u8 i1 = i & 0xF;
|
||||
u8 i2 = i >> 4;
|
||||
result[0] = i2 > 9 ? 'a' + i2 - 10 : '0' + i2;
|
||||
result[1] = i1 > 9 ? 'a' + i1 - 10 : '0' + i1;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GDBDebugServer::select_thread(u64 id)
|
||||
{
|
||||
//in case we have none at all
|
||||
selected_thread.reset();
|
||||
const auto on_select = [&](u32, cpu_thread& cpu)
|
||||
{
|
||||
return (id == ALL_THREADS) || (id == ANY_THREAD) || (cpu.id == id);
|
||||
};
|
||||
if (auto ppu = idm::select<ppu_thread>(on_select)) {
|
||||
selected_thread = ppu.ptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string GDBDebugServer::get_reg(std::shared_ptr<ppu_thread> thread, u32 rid)
|
||||
{
|
||||
std::string result;
|
||||
//ids from gdb/features/rs6000/powerpc-64.c
|
||||
//pc
|
||||
switch (rid) {
|
||||
case 64:
|
||||
return u64_to_padded_hex(thread->cia);
|
||||
//msr?
|
||||
case 65:
|
||||
return std::string(16, 'x');
|
||||
case 66:
|
||||
return u32_to_padded_hex(thread->cr_pack());
|
||||
case 67:
|
||||
return u64_to_padded_hex(thread->lr);
|
||||
case 68:
|
||||
return u64_to_padded_hex(thread->ctr);
|
||||
//xer
|
||||
case 69:
|
||||
return std::string(8, 'x');
|
||||
//fpscr
|
||||
case 70:
|
||||
return std::string(8, 'x');
|
||||
default:
|
||||
if (rid > 70) return "";
|
||||
return (rid > 31)
|
||||
? u64_to_padded_hex(reinterpret_cast<u64&>(thread->fpr[rid - 32])) //fpr
|
||||
: u64_to_padded_hex(thread->gpr[rid]); //gpr
|
||||
}
|
||||
}
|
||||
|
||||
bool GDBDebugServer::set_reg(std::shared_ptr<ppu_thread> thread, u32 rid, std::string value)
|
||||
{
|
||||
switch (rid) {
|
||||
case 64:
|
||||
thread->cia = static_cast<u32>(hex_to_u64(value));
|
||||
return true;
|
||||
//msr?
|
||||
case 65:
|
||||
return true;
|
||||
case 66:
|
||||
thread->cr_unpack(hex_to_u32(value));
|
||||
return true;
|
||||
case 67:
|
||||
thread->lr = hex_to_u64(value);
|
||||
return true;
|
||||
case 68:
|
||||
thread->ctr = hex_to_u64(value);
|
||||
return true;
|
||||
//xer
|
||||
case 69:
|
||||
return true;
|
||||
//fpscr
|
||||
case 70:
|
||||
return true;
|
||||
default:
|
||||
if (rid > 70) return false;
|
||||
if (rid > 31) {
|
||||
u64 val = hex_to_u64(value);
|
||||
thread->fpr[rid - 32] = reinterpret_cast<f64&>(val);
|
||||
} else {
|
||||
thread->gpr[rid] = hex_to_u64(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
u32 GDBDebugServer::get_reg_size(std::shared_ptr<ppu_thread> thread, u32 rid)
|
||||
{
|
||||
switch (rid) {
|
||||
case 66:
|
||||
case 69:
|
||||
case 70:
|
||||
return 4;
|
||||
default:
|
||||
if (rid > 70) {
|
||||
return 0;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
bool GDBDebugServer::send_reason()
|
||||
{
|
||||
return send_cmd_ack("S05");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_extended_mode(gdb_cmd & cmd)
|
||||
{
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_reason(gdb_cmd & cmd)
|
||||
{
|
||||
return send_reason();
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_supported(gdb_cmd & cmd)
|
||||
{
|
||||
return send_cmd_ack("PacketSize=1200");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_thread_info(gdb_cmd & cmd)
|
||||
{
|
||||
std::string result = "";
|
||||
const auto on_select = [&](u32, cpu_thread& cpu)
|
||||
{
|
||||
if (result.length()) {
|
||||
result += ",";
|
||||
}
|
||||
result += u64_to_padded_hex(static_cast<u64>(cpu.id));
|
||||
};
|
||||
idm::select<ppu_thread>(on_select);
|
||||
idm::select<ARMv7Thread>(on_select);
|
||||
idm::select<RawSPUThread>(on_select);
|
||||
idm::select<SPUThread>(on_select);
|
||||
|
||||
//todo: this may exceed max command length
|
||||
result = "m" + result + "l";
|
||||
|
||||
return send_cmd_ack(result);;
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_current_thread(gdb_cmd & cmd)
|
||||
{
|
||||
return send_cmd_ack(selected_thread.expired() ? "" : u64_to_padded_hex(selected_thread.lock()->id));
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_read_register(gdb_cmd & cmd)
|
||||
{
|
||||
select_thread(general_ops_thread_id);
|
||||
auto th = selected_thread.lock();
|
||||
if (th->id_type() == 1) {
|
||||
auto ppu = std::static_pointer_cast<ppu_thread>(th);
|
||||
u32 rid = hex_to_u32(cmd.data);
|
||||
std::string result = get_reg(ppu, rid);
|
||||
if (!result.length()) {
|
||||
gdbDebugServer.warning("Wrong register id %d", rid);
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
return send_cmd_ack(result);
|
||||
}
|
||||
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
|
||||
return send_cmd_ack("");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_write_register(gdb_cmd & cmd)
|
||||
{
|
||||
select_thread(general_ops_thread_id);
|
||||
auto th = selected_thread.lock();
|
||||
if (th->id_type() == 1) {
|
||||
auto ppu = std::static_pointer_cast<ppu_thread>(th);
|
||||
size_t eq_pos = cmd.data.find('=');
|
||||
if (eq_pos == std::string::npos) {
|
||||
gdbDebugServer.warning("Wrong write_register cmd data %s", cmd.data.c_str());
|
||||
return send_cmd_ack("E02");
|
||||
}
|
||||
u32 rid = hex_to_u32(cmd.data.substr(0, eq_pos));
|
||||
std::string value = cmd.data.substr(eq_pos + 1);
|
||||
if (!set_reg(ppu, rid, value)) {
|
||||
gdbDebugServer.warning("Wrong register id %d", rid);
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
|
||||
return send_cmd_ack("");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_read_memory(gdb_cmd & cmd)
|
||||
{
|
||||
size_t s = cmd.data.find(',');
|
||||
u32 addr = hex_to_u32(cmd.data.substr(0, s));
|
||||
u32 len = hex_to_u32(cmd.data.substr(s + 1));
|
||||
std::string result;
|
||||
result.reserve(len * 2);
|
||||
for (u32 i = 0; i < len; ++i) {
|
||||
if (vm::check_addr(addr + i)) {
|
||||
result += to_hexbyte(vm::read8(addr + i));
|
||||
} else {
|
||||
break;
|
||||
//result += "xx";
|
||||
}
|
||||
}
|
||||
if (len && !result.length()) {
|
||||
//nothing read
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
return send_cmd_ack(result);
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_write_memory(gdb_cmd & cmd)
|
||||
{
|
||||
size_t s = cmd.data.find(',');
|
||||
size_t s2 = cmd.data.find(':');
|
||||
if ((s == std::string::npos) || (s2 == std::string::npos)) {
|
||||
gdbDebugServer.warning("Malformed write memory request received: %s", cmd.data.c_str());
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
u32 addr = hex_to_u32(cmd.data.substr(0, s));
|
||||
u32 len = hex_to_u32(cmd.data.substr(s + 1, s2 - s - 1));
|
||||
const char* data_ptr = (cmd.data.c_str()) + s2 + 1;
|
||||
for (u32 i = 0; i < len; ++i) {
|
||||
if (vm::check_addr(addr + i)) {
|
||||
u8 val;
|
||||
int res = sscanf_s(data_ptr, "%02hhX", &val);
|
||||
if (!res) {
|
||||
gdbDebugServer.warning("Couldn't read u8 from string %s", data_ptr);
|
||||
return send_cmd_ack("E02");
|
||||
}
|
||||
data_ptr += 2;
|
||||
vm::write8(addr + i, val);
|
||||
} else {
|
||||
return send_cmd_ack("E03");
|
||||
}
|
||||
}
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_read_all_registers(gdb_cmd & cmd)
|
||||
{
|
||||
std::string result;
|
||||
select_thread(general_ops_thread_id);
|
||||
|
||||
auto th = selected_thread.lock();
|
||||
if (th->id_type() == 1) {
|
||||
auto ppu = std::static_pointer_cast<ppu_thread>(th);
|
||||
//68 64-bit registers, and 3 32-bit
|
||||
result.reserve(68*16 + 3*8);
|
||||
for (int i = 0; i < 71; ++i) {
|
||||
result += get_reg(ppu, i);
|
||||
}
|
||||
return send_cmd_ack(result);
|
||||
}
|
||||
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
|
||||
return send_cmd_ack("");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_write_all_registers(gdb_cmd & cmd)
|
||||
{
|
||||
select_thread(general_ops_thread_id);
|
||||
auto th = selected_thread.lock();
|
||||
if (th->id_type() == 1) {
|
||||
auto ppu = std::static_pointer_cast<ppu_thread>(th);
|
||||
int ptr = 0;
|
||||
for (int i = 0; i < 71; ++i) {
|
||||
int sz = get_reg_size(ppu, i);
|
||||
set_reg(ppu, i, cmd.data.substr(ptr, sz * 2));
|
||||
ptr += sz * 2;
|
||||
}
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
gdbDebugServer.warning("Unimplemented thread type %d", th->id_type());
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_set_thread_ops(gdb_cmd & cmd)
|
||||
{
|
||||
char type = cmd.data[0];
|
||||
std::string thread = cmd.data.substr(1);
|
||||
u64 id = thread == "-1" ? ALL_THREADS : hex_to_u64(thread);
|
||||
if (type == 'c') {
|
||||
continue_ops_thread_id = id;
|
||||
} else {
|
||||
general_ops_thread_id = id;
|
||||
}
|
||||
if (select_thread(id)) {
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
gdbDebugServer.warning("Client asked to use thread %llx for %s, but no matching thread was found", id, type == 'c' ? "continue ops" : "general ops");
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_attached_to_what(gdb_cmd & cmd)
|
||||
{
|
||||
//creating processes from client is not available yet
|
||||
return send_cmd_ack("1");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_kill(gdb_cmd & cmd)
|
||||
{
|
||||
Emu.Stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_continue_support(gdb_cmd & cmd)
|
||||
{
|
||||
return send_cmd_ack("vCont;c;s;C;S");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_vcont(gdb_cmd & cmd)
|
||||
{
|
||||
//todo: handle multiple actions and thread ids
|
||||
this->from_breakpoint = false;
|
||||
if (cmd.data[1] == 'c') {
|
||||
select_thread(continue_ops_thread_id);
|
||||
auto ppu = std::static_pointer_cast<ppu_thread>(selected_thread.lock());
|
||||
ppu->state -= cpu_flag::dbg_pause;
|
||||
if (Emu.IsPaused()) {
|
||||
Emu.Resume();
|
||||
}
|
||||
thread_ctrl::wait();
|
||||
//we are in all-stop mode
|
||||
Emu.Pause();
|
||||
return send_reason();
|
||||
} else if (cmd.data[1] == 's') {
|
||||
select_thread(continue_ops_thread_id);
|
||||
auto ppu = std::static_pointer_cast<ppu_thread>(selected_thread.lock());
|
||||
ppu->state += cpu_flag::dbg_step;
|
||||
ppu->state -= cpu_flag::dbg_pause;
|
||||
if (Emu.IsPaused()) {
|
||||
Emu.Resume();
|
||||
} else {
|
||||
ppu->notify();
|
||||
}
|
||||
thread_ctrl::wait();
|
||||
//we are in all-stop mode
|
||||
Emu.Pause();
|
||||
return send_reason();
|
||||
}
|
||||
return send_cmd_ack("");
|
||||
}
|
||||
|
||||
static const u32 INVALID_PTR = 0xffffffff;
|
||||
|
||||
bool GDBDebugServer::cmd_set_breakpoint(gdb_cmd & cmd)
|
||||
{
|
||||
char type = cmd.data[0];
|
||||
//software breakpoint
|
||||
if (type == '0') {
|
||||
u32 addr = INVALID_PTR;
|
||||
if (cmd.data.find(';') != std::string::npos) {
|
||||
gdbDebugServer.warning("Received request to set breakpoint with condition, but they are not supported");
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
sscanf_s(cmd.data.c_str(), "0,%x", &addr);
|
||||
if (addr == INVALID_PTR) {
|
||||
gdbDebugServer.warning("Can't parse breakpoint request, data: %s", cmd.data.c_str());
|
||||
return send_cmd_ack("E02");
|
||||
}
|
||||
ppu_set_breakpoint(addr);
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
//other breakpoint types are not supported
|
||||
return send_cmd_ack("");
|
||||
}
|
||||
|
||||
bool GDBDebugServer::cmd_remove_breakpoint(gdb_cmd & cmd)
|
||||
{
|
||||
char type = cmd.data[0];
|
||||
//software breakpoint
|
||||
if (type == '0') {
|
||||
u32 addr = INVALID_PTR;
|
||||
sscanf_s(cmd.data.c_str(), "0,%x", &addr);
|
||||
if (addr == INVALID_PTR) {
|
||||
gdbDebugServer.warning("Can't parse breakpoint remove request, data: %s", cmd.data.c_str());
|
||||
return send_cmd_ack("E01");
|
||||
}
|
||||
ppu_remove_breakpoint(addr);
|
||||
return send_cmd_ack("OK");
|
||||
}
|
||||
//other breakpoint types are not supported
|
||||
return send_cmd_ack("");
|
||||
|
||||
}
|
||||
|
||||
#define PROCESS_CMD(cmds,handler) if (cmd.cmd == cmds) { if (!handler(cmd)) break; else continue; }
|
||||
|
||||
void GDBDebugServer::on_task()
|
||||
{
|
||||
sock_init();
|
||||
|
||||
start_server();
|
||||
|
||||
while (!stop) {
|
||||
sockaddr_in client;
|
||||
socklen_t client_len = sizeof(client);
|
||||
client_socket = accept(server_socket, (struct sockaddr *) &client, &client_len);
|
||||
|
||||
if (client_socket == INVALID_SOCKET) {
|
||||
if (check_errno_again()) {
|
||||
thread_ctrl::wait_for(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
gdbDebugServer.error("Could not establish new connection\n");
|
||||
return;
|
||||
}
|
||||
//stop immediately
|
||||
Emu.Pause();
|
||||
|
||||
try {
|
||||
char hostbuf[32];
|
||||
inet_ntop(client.sin_family, reinterpret_cast<void*>(&client.sin_addr), hostbuf, 32);
|
||||
gdbDebugServer.success("Got connection to GDB debug server from %s:%d", hostbuf, client.sin_port);
|
||||
|
||||
gdb_cmd cmd;
|
||||
|
||||
while (!stop) {
|
||||
if (!read_cmd(cmd)) {
|
||||
break;
|
||||
}
|
||||
gdbDebugServer.trace("Command %s with data %s received", cmd.cmd.c_str(), cmd.data.c_str());
|
||||
PROCESS_CMD("!", cmd_extended_mode);
|
||||
PROCESS_CMD("?", cmd_reason);
|
||||
PROCESS_CMD("qSupported", cmd_supported);
|
||||
PROCESS_CMD("qfThreadInfo", cmd_thread_info);
|
||||
PROCESS_CMD("qC", cmd_current_thread);
|
||||
PROCESS_CMD("p", cmd_read_register);
|
||||
PROCESS_CMD("P", cmd_write_register);
|
||||
PROCESS_CMD("m", cmd_read_memory);
|
||||
PROCESS_CMD("M", cmd_write_memory);
|
||||
PROCESS_CMD("g", cmd_read_all_registers);
|
||||
PROCESS_CMD("G", cmd_write_all_registers);
|
||||
PROCESS_CMD("H", cmd_set_thread_ops);
|
||||
PROCESS_CMD("qAttached", cmd_attached_to_what);
|
||||
PROCESS_CMD("k", cmd_kill);
|
||||
PROCESS_CMD("vCont?", cmd_continue_support);
|
||||
PROCESS_CMD("vCont", cmd_vcont);
|
||||
PROCESS_CMD("z", cmd_remove_breakpoint);
|
||||
PROCESS_CMD("Z", cmd_set_breakpoint);
|
||||
|
||||
gdbDebugServer.trace("Unsupported command received %s", cmd.cmd.c_str());
|
||||
if (!send_cmd_ack("")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::runtime_error& e)
|
||||
{
|
||||
if (client_socket) {
|
||||
closesocket(client_socket);
|
||||
client_socket = 0;
|
||||
}
|
||||
gdbDebugServer.error(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef PROCESS_CMD
|
||||
|
||||
void GDBDebugServer::on_exit()
|
||||
{
|
||||
if (server_socket) {
|
||||
closesocket(server_socket);
|
||||
}
|
||||
if (client_socket) {
|
||||
closesocket(client_socket);
|
||||
}
|
||||
sock_quit();
|
||||
}
|
||||
|
||||
std::string GDBDebugServer::get_name() const
|
||||
{
|
||||
return "GDBDebugger";
|
||||
}
|
||||
|
||||
void GDBDebugServer::on_stop()
|
||||
{
|
||||
this->stop = true;
|
||||
//just in case we are waiting for breakpoint
|
||||
this->notify();
|
||||
named_thread::on_stop();
|
||||
}
|
||||
|
||||
u32 g_gdb_debugger_id = 0;
|
||||
|
||||
#ifndef _WIN32
|
||||
#undef sscanf_s
|
||||
#endif
|
||||
|
||||
#undef HEX_U32
|
||||
#undef HEX_U64
|
||||
|
||||
#endif
|
||||
@@ -1,131 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_GDB_DEBUGGER
|
||||
|
||||
#include "Thread.h"
|
||||
#include <Emu/IdManager.h>
|
||||
#include "Emu/CPU/CPUThread.h"
|
||||
#include "Emu/Cell/PPUThread.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
using socket_t = SOCKET;
|
||||
#else
|
||||
using socket_t = int;
|
||||
#endif
|
||||
|
||||
typedef struct gdb_cmd {
|
||||
std::string cmd;
|
||||
std::string data;
|
||||
u8 checksum;
|
||||
} gdb_cmd;
|
||||
|
||||
class wrong_checksum_exception : public std::runtime_error {
|
||||
public:
|
||||
wrong_checksum_exception(char const* const message) : runtime_error(message) {}
|
||||
};
|
||||
|
||||
const u64 ALL_THREADS = 0xffffffffffffffff;
|
||||
const u64 ANY_THREAD = 0;
|
||||
|
||||
class GDBDebugServer : public named_thread {
|
||||
|
||||
socket_t server_socket;
|
||||
socket_t client_socket;
|
||||
std::weak_ptr<cpu_thread> selected_thread;
|
||||
u64 continue_ops_thread_id = ANY_THREAD;
|
||||
u64 general_ops_thread_id = ANY_THREAD;
|
||||
|
||||
//initialize server socket and start listening
|
||||
void start_server();
|
||||
//read at most cnt bytes to buf, returns nubmer of bytes actually read
|
||||
int read(void* buf, int cnt);
|
||||
//reads one character
|
||||
char read_char();
|
||||
//reads pairs of hex characters and returns their integer value
|
||||
u8 read_hexbyte();
|
||||
//tries to read command, throws exceptions if anything goes wrong
|
||||
void try_read_cmd(gdb_cmd& out_cmd);
|
||||
//reads commands until receiveing one with valid checksum
|
||||
//in case of other exception (i.e. wrong first char of command)
|
||||
//it will log exception text and return false
|
||||
//in that case best for caller would be to stop reading, because
|
||||
//chance of getting correct command is low
|
||||
bool read_cmd(gdb_cmd& out_cmd);
|
||||
//send cnt bytes from buf to client
|
||||
void send(const char* buf, int cnt);
|
||||
//send character to client
|
||||
void send_char(char c);
|
||||
//acknowledge packet, either as accepted or declined
|
||||
void ack(bool accepted);
|
||||
//sends command body cmd to client
|
||||
void send_cmd(const std::string & cmd);
|
||||
//sends command to client until receives positive acknowledgement
|
||||
//returns false in case some error happened, and command wasn't sent
|
||||
bool send_cmd_ack(const std::string & cmd);
|
||||
//appends encoded char c to string str, and returns checksum. encoded byte can occupy 2 bytes
|
||||
static u8 append_encoded_char(char c, std::string& str);
|
||||
//convert u8 to 2 byte hexademical representation
|
||||
static std::string to_hexbyte(u8 i);
|
||||
//choose thread, support ALL_THREADS and ANY_THREAD values, returns true if some thread was selected
|
||||
bool select_thread(u64 id);
|
||||
//returns register value as hex string by register id (in gdb), in case of wrong id returns empty string
|
||||
static std::string get_reg(std::shared_ptr<ppu_thread> thread, u32 rid);
|
||||
//sets register value to hex string by register id (in gdb), in case of wrong id returns false
|
||||
static bool set_reg(std::shared_ptr<ppu_thread> thread, u32 rid, std::string value);
|
||||
//returns size of register with id rid in bytes, zero if invalid rid is provided
|
||||
static u32 get_reg_size(std::shared_ptr<ppu_thread> thread, u32 rid);
|
||||
//send reason of stop, returns false if sending response failed
|
||||
bool send_reason();
|
||||
|
||||
//commands
|
||||
bool cmd_extended_mode(gdb_cmd& cmd);
|
||||
bool cmd_reason(gdb_cmd& cmd);
|
||||
bool cmd_supported(gdb_cmd& cmd);
|
||||
bool cmd_thread_info(gdb_cmd& cmd);
|
||||
bool cmd_current_thread(gdb_cmd& cmd);
|
||||
bool cmd_read_register(gdb_cmd& cmd);
|
||||
bool cmd_write_register(gdb_cmd& cmd);
|
||||
bool cmd_read_memory(gdb_cmd& cmd);
|
||||
bool cmd_write_memory(gdb_cmd& cmd);
|
||||
bool cmd_read_all_registers(gdb_cmd& cmd);
|
||||
bool cmd_write_all_registers(gdb_cmd& cmd);
|
||||
bool cmd_set_thread_ops(gdb_cmd& cmd);
|
||||
bool cmd_attached_to_what(gdb_cmd& cmd);
|
||||
bool cmd_kill(gdb_cmd& cmd);
|
||||
bool cmd_continue_support(gdb_cmd& cmd);
|
||||
bool cmd_vcont(gdb_cmd& cmd);
|
||||
bool cmd_set_breakpoint(gdb_cmd& cmd);
|
||||
bool cmd_remove_breakpoint(gdb_cmd& cmd);
|
||||
|
||||
protected:
|
||||
void on_task() override final;
|
||||
void on_exit() override final;
|
||||
|
||||
public:
|
||||
static const u32 id_base = 1;
|
||||
static const u32 id_step = 1;
|
||||
static const u32 id_count = 0x100000;
|
||||
|
||||
bool from_breakpoint = true;
|
||||
bool stop = false;
|
||||
|
||||
virtual std::string get_name() const;
|
||||
virtual void on_stop() override final;
|
||||
};
|
||||
|
||||
extern u32 g_gdb_debugger_id;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "GNU.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/types.h>
|
||||
#include <sys/_types/_timespec.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach_time.h>
|
||||
#undef CPU_STATE_MAX
|
||||
|
||||
#define MT_NANO (+1.0E-9)
|
||||
#define MT_GIGA UINT64_C(1000000000)
|
||||
|
||||
// TODO create a list of timers,
|
||||
static double mt_timebase = 0.0;
|
||||
static uint64_t mt_timestart = 0;
|
||||
|
||||
// TODO be more careful in a multithreaded environement
|
||||
int clock_gettime(clockid_t clk_id, struct timespec *tp)
|
||||
{
|
||||
kern_return_t retval = KERN_SUCCESS;
|
||||
if( clk_id == TIMER_ABSTIME)
|
||||
{
|
||||
if (!mt_timestart) { // only one timer, initilized on the first call to the TIMER
|
||||
mach_timebase_info_data_t tb = { 0 };
|
||||
mach_timebase_info(&tb);
|
||||
mt_timebase = tb.numer;
|
||||
mt_timebase /= tb.denom;
|
||||
mt_timestart = mach_absolute_time();
|
||||
}
|
||||
|
||||
double diff = (mach_absolute_time() - mt_timestart) * mt_timebase;
|
||||
tp->tv_sec = diff * MT_NANO;
|
||||
tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA);
|
||||
}
|
||||
else // other clk_ids are mapped to the coresponding mach clock_service
|
||||
{
|
||||
clock_serv_t cclock;
|
||||
mach_timespec_t mts;
|
||||
|
||||
host_get_clock_service(mach_host_self(), clk_id, &cclock);
|
||||
retval = clock_get_time(cclock, &mts);
|
||||
mach_port_deallocate(mach_task_self(), cclock);
|
||||
|
||||
tp->tv_sec = mts.tv_sec;
|
||||
tp->tv_nsec = mts.tv_nsec;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
#endif /* __APPLE__ */
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
#pragma once
|
||||
|
||||
#include <emmintrin.h>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
||||
#define thread_local __declspec(thread)
|
||||
#elif __APPLE__
|
||||
#define thread_local __thread
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define never_inline __declspec(noinline)
|
||||
#else
|
||||
#define never_inline __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define safe_buffers __declspec(safebuffers)
|
||||
#else
|
||||
#define safe_buffers
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define force_inline __forceinline
|
||||
#else
|
||||
#define force_inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
||||
#define alignas(x) _CRT_ALIGN(x)
|
||||
#endif
|
||||
|
||||
#if defined(__GNUG__)
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdint>
|
||||
|
||||
#define _fpclass(x) std::fpclassify(x)
|
||||
#define INFINITE 0xFFFFFFFF
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// XXX only supports a single timer
|
||||
#define TIMER_ABSTIME -1
|
||||
/* The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR
|
||||
being appropriate or not.
|
||||
http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html */
|
||||
#define CLOCK_REALTIME 1 // #define CALENDAR_CLOCK 1 from mach/clock_types.h
|
||||
#define CLOCK_MONOTONIC 0 // #define SYSTEM_CLOCK 0
|
||||
|
||||
typedef int clockid_t;
|
||||
|
||||
/* the mach kernel uses struct mach_timespec, so struct timespec
|
||||
is loaded from <sys/_types/_timespec.h> for compatability */
|
||||
// struct timespec { time_t tv_sec; long tv_nsec; };
|
||||
|
||||
int clock_gettime(clockid_t clk_id, struct timespec *tp);
|
||||
|
||||
#endif /* __APPLE__ */
|
||||
#endif /* __GNUG__ */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
// Unsigned 128-bit integer implementation
|
||||
struct alignas(16) u128
|
||||
{
|
||||
std::uint64_t lo, hi;
|
||||
|
||||
u128() = default;
|
||||
|
||||
u128(const u128&) = default;
|
||||
|
||||
u128(std::uint64_t l)
|
||||
: lo(l)
|
||||
, hi(0)
|
||||
{
|
||||
}
|
||||
|
||||
u128 operator +(const u128& r) const
|
||||
{
|
||||
u128 value;
|
||||
_addcarry_u64(_addcarry_u64(0, r.lo, lo, &value.lo), r.hi, hi, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
friend u128 operator +(const u128& l, std::uint64_t r)
|
||||
{
|
||||
u128 value;
|
||||
_addcarry_u64(_addcarry_u64(0, r, l.lo, &value.lo), l.hi, 0, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
friend u128 operator +(std::uint64_t l, const u128& r)
|
||||
{
|
||||
u128 value;
|
||||
_addcarry_u64(_addcarry_u64(0, r.lo, l, &value.lo), 0, r.hi, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
u128 operator -(const u128& r) const
|
||||
{
|
||||
u128 value;
|
||||
_subborrow_u64(_subborrow_u64(0, r.lo, lo, &value.lo), r.hi, hi, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
friend u128 operator -(const u128& l, std::uint64_t r)
|
||||
{
|
||||
u128 value;
|
||||
_subborrow_u64(_subborrow_u64(0, r, l.lo, &value.lo), 0, l.hi, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
friend u128 operator -(std::uint64_t l, const u128& r)
|
||||
{
|
||||
u128 value;
|
||||
_subborrow_u64(_subborrow_u64(0, r.lo, l, &value.lo), r.hi, 0, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
u128 operator +() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128 operator -() const
|
||||
{
|
||||
u128 value;
|
||||
_subborrow_u64(_subborrow_u64(0, lo, 0, &value.lo), hi, 0, &value.hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
u128& operator ++()
|
||||
{
|
||||
_addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi);
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128 operator ++(int)
|
||||
{
|
||||
u128 value = *this;
|
||||
_addcarry_u64(_addcarry_u64(0, 1, lo, &lo), 0, hi, &hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
u128& operator --()
|
||||
{
|
||||
_subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi);
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128 operator --(int)
|
||||
{
|
||||
u128 value = *this;
|
||||
_subborrow_u64(_subborrow_u64(0, 1, lo, &lo), 0, hi, &hi);
|
||||
return value;
|
||||
}
|
||||
|
||||
u128 operator ~() const
|
||||
{
|
||||
u128 value;
|
||||
value.lo = ~lo;
|
||||
value.hi = ~hi;
|
||||
return value;
|
||||
}
|
||||
|
||||
u128 operator &(const u128& r) const
|
||||
{
|
||||
u128 value;
|
||||
value.lo = lo & r.lo;
|
||||
value.hi = hi & r.hi;
|
||||
return value;
|
||||
}
|
||||
|
||||
u128 operator |(const u128& r) const
|
||||
{
|
||||
u128 value;
|
||||
value.lo = lo | r.lo;
|
||||
value.hi = hi | r.hi;
|
||||
return value;
|
||||
}
|
||||
|
||||
u128 operator ^(const u128& r) const
|
||||
{
|
||||
u128 value;
|
||||
value.lo = lo ^ r.lo;
|
||||
value.hi = hi ^ r.hi;
|
||||
return value;
|
||||
}
|
||||
|
||||
u128& operator +=(const u128& r)
|
||||
{
|
||||
_addcarry_u64(_addcarry_u64(0, r.lo, lo, &lo), r.hi, hi, &hi);
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128& operator +=(uint64_t r)
|
||||
{
|
||||
_addcarry_u64(_addcarry_u64(0, r, lo, &lo), 0, hi, &hi);
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128& operator &=(const u128& r)
|
||||
{
|
||||
lo &= r.lo;
|
||||
hi &= r.hi;
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128& operator |=(const u128& r)
|
||||
{
|
||||
lo |= r.lo;
|
||||
hi |= r.hi;
|
||||
return *this;
|
||||
}
|
||||
|
||||
u128& operator ^=(const u128& r)
|
||||
{
|
||||
lo ^= r.lo;
|
||||
hi ^= r.hi;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
inline std::uint32_t cntlz32(std::uint32_t arg)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long res;
|
||||
return _BitScanReverse(&res, arg) ? res ^ 31 : 32;
|
||||
#else
|
||||
return arg ? __builtin_clzll(arg) - 32 : 32;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::uint64_t cntlz64(std::uint64_t arg)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
unsigned long res;
|
||||
return _BitScanReverse64(&res, arg) ? res ^ 63 : 64;
|
||||
#else
|
||||
return arg ? __builtin_clzll(arg) : 64;
|
||||
#endif
|
||||
}
|
||||
|
||||
// compare 16 packed unsigned bytes (greater than)
|
||||
inline __m128i sse_cmpgt_epu8(__m128i A, __m128i B)
|
||||
{
|
||||
// (A xor 0x80) > (B xor 0x80)
|
||||
const auto sign = _mm_set1_epi32(0x80808080);
|
||||
return _mm_cmpgt_epi8(_mm_xor_si128(A, sign), _mm_xor_si128(B, sign));
|
||||
}
|
||||
|
||||
inline __m128i sse_cmpgt_epu16(__m128i A, __m128i B)
|
||||
{
|
||||
const auto sign = _mm_set1_epi32(0x80008000);
|
||||
return _mm_cmpgt_epi16(_mm_xor_si128(A, sign), _mm_xor_si128(B, sign));
|
||||
}
|
||||
|
||||
inline __m128i sse_cmpgt_epu32(__m128i A, __m128i B)
|
||||
{
|
||||
const auto sign = _mm_set1_epi32(0x80000000);
|
||||
return _mm_cmpgt_epi32(_mm_xor_si128(A, sign), _mm_xor_si128(B, sign));
|
||||
}
|
||||
|
||||
inline __m128 sse_exp2_ps(__m128 A)
|
||||
{
|
||||
const auto x0 = _mm_max_ps(_mm_min_ps(A, _mm_set1_ps(127.4999961f)), _mm_set1_ps(-127.4999961f));
|
||||
const auto x1 = _mm_add_ps(x0, _mm_set1_ps(0.5f));
|
||||
const auto x2 = _mm_sub_epi32(_mm_cvtps_epi32(x1), _mm_and_si128(_mm_castps_si128(_mm_cmpnlt_ps(_mm_setzero_ps(), x1)), _mm_set1_epi32(1)));
|
||||
const auto x3 = _mm_sub_ps(x0, _mm_cvtepi32_ps(x2));
|
||||
const auto x4 = _mm_mul_ps(x3, x3);
|
||||
const auto x5 = _mm_mul_ps(x3, _mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(x4, _mm_set1_ps(0.023093347705f)), _mm_set1_ps(20.20206567f)), x4), _mm_set1_ps(1513.906801f)));
|
||||
const auto x6 = _mm_mul_ps(x5, _mm_rcp_ps(_mm_sub_ps(_mm_add_ps(_mm_mul_ps(_mm_set1_ps(233.1842117f), x4), _mm_set1_ps(4368.211667f)), x5)));
|
||||
return _mm_mul_ps(_mm_add_ps(_mm_add_ps(x6, x6), _mm_set1_ps(1.0f)), _mm_castsi128_ps(_mm_slli_epi32(_mm_add_epi32(x2, _mm_set1_epi32(127)), 23)));
|
||||
}
|
||||
|
||||
inline __m128 sse_log2_ps(__m128 A)
|
||||
{
|
||||
const auto _1 = _mm_set1_ps(1.0f);
|
||||
const auto _c = _mm_set1_ps(1.442695040f);
|
||||
const auto x0 = _mm_max_ps(A, _mm_castsi128_ps(_mm_set1_epi32(0x00800000)));
|
||||
const auto x1 = _mm_or_ps(_mm_and_ps(x0, _mm_castsi128_ps(_mm_set1_epi32(0x807fffff))), _1);
|
||||
const auto x2 = _mm_rcp_ps(_mm_add_ps(x1, _1));
|
||||
const auto x3 = _mm_mul_ps(_mm_sub_ps(x1, _1), x2);
|
||||
const auto x4 = _mm_add_ps(x3, x3);
|
||||
const auto x5 = _mm_mul_ps(x4, x4);
|
||||
const auto x6 = _mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(_mm_set1_ps(-0.7895802789f), x5), _mm_set1_ps(16.38666457f)), x5), _mm_set1_ps(-64.1409953f));
|
||||
const auto x7 = _mm_rcp_ps(_mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(_mm_set1_ps(-35.67227983f), x5), _mm_set1_ps(312.0937664f)), x5), _mm_set1_ps(-769.6919436f)));
|
||||
const auto x8 = _mm_cvtepi32_ps(_mm_sub_epi32(_mm_srli_epi32(_mm_castps_si128(x0), 23), _mm_set1_epi32(127)));
|
||||
return _mm_add_ps(_mm_mul_ps(_mm_mul_ps(_mm_mul_ps(_mm_mul_ps(x5, x6), x7), x4), _c), _mm_add_ps(_mm_mul_ps(x4, _c), x8));
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define GSL_THROW_ON_CONTRACT_VIOLATION
|
||||
|
||||
#pragma push_macro("new")
|
||||
#undef new
|
||||
#include <gsl.h>
|
||||
#pragma pop_macro("new")
|
||||
#undef Expects
|
||||
#undef Ensures
|
||||
@@ -1,532 +0,0 @@
|
||||
#ifdef LLVM_AVAILABLE
|
||||
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
|
||||
#include "types.h"
|
||||
#include "StrFmt.h"
|
||||
#include "File.h"
|
||||
#include "Log.h"
|
||||
#include "mutex.h"
|
||||
#include "sysinfo.h"
|
||||
#include "VirtualMemory.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push, 0)
|
||||
#endif
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
#include "llvm/Support/FormattedStream.h"
|
||||
#include "llvm/ExecutionEngine/ExecutionEngine.h"
|
||||
#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
|
||||
#include "llvm/ExecutionEngine/JITEventListener.h"
|
||||
#include "llvm/ExecutionEngine/ObjectCache.h"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include "JIT.h"
|
||||
|
||||
// Memory manager mutex
|
||||
shared_mutex s_mutex;
|
||||
|
||||
// Size of virtual memory area reserved: 512 MB
|
||||
static const u64 s_memory_size = 0x20000000;
|
||||
|
||||
// Try to reserve a portion of virtual memory in the first 2 GB address space beforehand, if possible.
|
||||
static void* const s_memory = []() -> void*
|
||||
{
|
||||
llvm::InitializeNativeTarget();
|
||||
llvm::InitializeNativeTargetAsmPrinter();
|
||||
LLVMLinkInMCJIT();
|
||||
|
||||
#ifdef MAP_32BIT
|
||||
auto ptr = ::mmap(nullptr, s_memory_size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_32BIT, -1, 0);
|
||||
if (ptr != MAP_FAILED)
|
||||
return ptr;
|
||||
#else
|
||||
for (u64 addr = 0x10000000; addr <= 0x80000000 - s_memory_size; addr += 0x1000000)
|
||||
{
|
||||
if (auto ptr = utils::memory_reserve(s_memory_size, (void*)addr))
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return utils::memory_reserve(s_memory_size);
|
||||
}();
|
||||
|
||||
static void* s_next = s_memory;
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::deque<std::vector<RUNTIME_FUNCTION>> s_unwater;
|
||||
static std::vector<std::vector<RUNTIME_FUNCTION>> s_unwind; // .pdata
|
||||
#else
|
||||
static std::deque<std::tuple<u8*, u64, std::size_t>> s_unfire;
|
||||
#endif
|
||||
|
||||
// Reset memory manager
|
||||
extern void jit_finalize()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
for (auto&& unwind : s_unwind)
|
||||
{
|
||||
if (!RtlDeleteFunctionTable(unwind.data()))
|
||||
{
|
||||
LOG_FATAL(GENERAL, "RtlDeleteFunctionTable() failed! Error %u", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
s_unwind.clear();
|
||||
#else
|
||||
struct MemoryManager : llvm::RTDyldMemoryManager
|
||||
{
|
||||
u8* allocateCodeSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8* allocateDataSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name, bool is_ro) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool finalizeMemory(std::string* = nullptr) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} mem;
|
||||
|
||||
for (auto&& t : s_unfire)
|
||||
{
|
||||
mem.deregisterEHFrames(std::get<0>(t), std::get<1>(t), std::get<2>(t));
|
||||
}
|
||||
|
||||
s_unfire.clear();
|
||||
#endif
|
||||
|
||||
utils::memory_decommit(s_memory, s_memory_size);
|
||||
|
||||
s_next = s_memory;
|
||||
}
|
||||
|
||||
// Helper class
|
||||
struct MemoryManager : llvm::RTDyldMemoryManager
|
||||
{
|
||||
std::unordered_map<std::string, u64>& m_link;
|
||||
|
||||
std::array<u8, 16>* m_tramps{};
|
||||
|
||||
u8* m_code_addr{}; // TODO
|
||||
|
||||
MemoryManager(std::unordered_map<std::string, u64>& table)
|
||||
: m_link(table)
|
||||
{
|
||||
}
|
||||
|
||||
[[noreturn]] static void null()
|
||||
{
|
||||
fmt::throw_exception("Null function" HERE);
|
||||
}
|
||||
|
||||
llvm::JITSymbol findSymbol(const std::string& name) override
|
||||
{
|
||||
auto& addr = m_link[name];
|
||||
|
||||
// Find function address
|
||||
if (!addr)
|
||||
{
|
||||
addr = RTDyldMemoryManager::getSymbolAddress(name);
|
||||
|
||||
if (addr)
|
||||
{
|
||||
LOG_WARNING(GENERAL, "LLVM: Symbol requested: %s -> 0x%016llx", name, addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(GENERAL, "LLVM: Linkage failed: %s", name);
|
||||
addr = (u64)null;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify address for small code model
|
||||
if ((u64)s_memory > 0x80000000 - s_memory_size ? (u64)addr - (u64)s_memory >= s_memory_size : addr >= 0x80000000)
|
||||
{
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
// Allocate memory for trampolines
|
||||
if (!m_tramps)
|
||||
{
|
||||
m_tramps = reinterpret_cast<decltype(m_tramps)>(s_next);
|
||||
utils::memory_commit(s_next, 4096, utils::protection::wx);
|
||||
s_next = (u8*)((u64)s_next + 4096);
|
||||
}
|
||||
|
||||
// Create a trampoline
|
||||
auto& data = *m_tramps++;
|
||||
data[0x0] = 0xff; // JMP [rip+2]
|
||||
data[0x1] = 0x25;
|
||||
data[0x2] = 0x02;
|
||||
data[0x3] = 0x00;
|
||||
data[0x4] = 0x00;
|
||||
data[0x5] = 0x00;
|
||||
data[0x6] = 0x48; // MOV rax, imm64 (not executed)
|
||||
data[0x7] = 0xb8;
|
||||
std::memcpy(data.data() + 8, &addr, 8);
|
||||
addr = (u64)&data;
|
||||
|
||||
// Reset pointer (memory page exhausted)
|
||||
if (((u64)m_tramps % 4096) == 0)
|
||||
{
|
||||
m_tramps = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return {addr, llvm::JITSymbolFlags::Exported};
|
||||
}
|
||||
|
||||
u8* allocateCodeSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name) override
|
||||
{
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
// Simple allocation
|
||||
const u64 next = ::align((u64)s_next + size, 4096);
|
||||
|
||||
if (next > (u64)s_memory + s_memory_size)
|
||||
{
|
||||
LOG_FATAL(GENERAL, "LLVM: Out of memory (size=0x%llx, aligned 0x%x)", size, align);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
utils::memory_commit(s_next, size, utils::protection::wx);
|
||||
m_code_addr = (u8*)s_next;
|
||||
|
||||
LOG_NOTICE(GENERAL, "LLVM: Code section %u '%s' allocated -> %p (size=0x%llx, aligned 0x%x)", sec_id, sec_name.data(), s_next, size, align);
|
||||
return (u8*)std::exchange(s_next, (void*)next);
|
||||
}
|
||||
|
||||
u8* allocateDataSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name, bool is_ro) override
|
||||
{
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
// Simple allocation
|
||||
const u64 next = ::align((u64)s_next + size, 4096);
|
||||
|
||||
if (next > (u64)s_memory + s_memory_size)
|
||||
{
|
||||
LOG_FATAL(GENERAL, "LLVM: Out of memory (size=0x%llx, aligned 0x%x)", size, align);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!is_ro)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "LLVM: Writeable data section not supported!");
|
||||
}
|
||||
|
||||
utils::memory_commit(s_next, size);
|
||||
|
||||
LOG_NOTICE(GENERAL, "LLVM: Data section %u '%s' allocated -> %p (size=0x%llx, aligned 0x%x, %s)", sec_id, sec_name.data(), s_next, size, align, is_ro ? "ro" : "rw");
|
||||
return (u8*)std::exchange(s_next, (void*)next);
|
||||
}
|
||||
|
||||
bool finalizeMemory(std::string* = nullptr) override
|
||||
{
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
// TODO: make only read-only sections read-only
|
||||
//#ifdef _WIN32
|
||||
// DWORD op;
|
||||
// VirtualProtect(s_memory, (u64)m_next - (u64)s_memory, PAGE_READONLY, &op);
|
||||
// VirtualProtect(s_code_addr, s_code_size, PAGE_EXECUTE_READ, &op);
|
||||
//#else
|
||||
// ::mprotect(s_memory, (u64)m_next - (u64)s_memory, PROT_READ);
|
||||
// ::mprotect(s_code_addr, s_code_size, PROT_READ | PROT_EXEC);
|
||||
//#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void registerEHFrames(u8* addr, u64 load_addr, std::size_t size) override
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
// Use s_memory as a BASE, compute the difference
|
||||
const u64 unwind_diff = (u64)addr - (u64)s_memory;
|
||||
|
||||
// Fix RUNTIME_FUNCTION records (.pdata section)
|
||||
auto pdata = std::move(s_unwater.front());
|
||||
s_unwater.pop_front();
|
||||
|
||||
for (auto& rf : pdata)
|
||||
{
|
||||
rf.UnwindData += static_cast<DWORD>(unwind_diff);
|
||||
}
|
||||
|
||||
// Register .xdata UNWIND_INFO structs
|
||||
if (!RtlAddFunctionTable(pdata.data(), (DWORD)pdata.size(), (u64)s_memory))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "RtlAddFunctionTable() failed! Error %u", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
s_unwind.emplace_back(std::move(pdata));
|
||||
}
|
||||
#else
|
||||
s_unfire.push_front(std::make_tuple(addr, load_addr, size));
|
||||
#endif
|
||||
|
||||
return RTDyldMemoryManager::registerEHFrames(addr, load_addr, size);
|
||||
}
|
||||
|
||||
void deregisterEHFrames(u8* addr, u64 load_addr, std::size_t size) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class
|
||||
struct EventListener : llvm::JITEventListener
|
||||
{
|
||||
MemoryManager& m_mem;
|
||||
|
||||
EventListener(MemoryManager& mem)
|
||||
: m_mem(mem)
|
||||
{
|
||||
}
|
||||
|
||||
void NotifyObjectEmitted(const llvm::object::ObjectFile& obj, const llvm::RuntimeDyld::LoadedObjectInfo& inf) override
|
||||
{
|
||||
#ifdef _WIN32
|
||||
for (auto it = obj.section_begin(), end = obj.section_end(); it != end; ++it)
|
||||
{
|
||||
llvm::StringRef name;
|
||||
it->getName(name);
|
||||
|
||||
if (name == ".pdata")
|
||||
{
|
||||
llvm::StringRef data;
|
||||
it->getContents(data);
|
||||
|
||||
std::vector<RUNTIME_FUNCTION> rfs(data.size() / sizeof(RUNTIME_FUNCTION));
|
||||
|
||||
auto offsets = reinterpret_cast<DWORD*>(rfs.data());
|
||||
|
||||
// Initialize .pdata section using relocation info
|
||||
for (auto ri = it->relocation_begin(), end = it->relocation_end(); ri != end; ++ri)
|
||||
{
|
||||
if (ri->getType() == 3 /*R_X86_64_GOT32*/)
|
||||
{
|
||||
const u64 value = *reinterpret_cast<const DWORD*>(data.data() + ri->getOffset());
|
||||
offsets[ri->getOffset() / sizeof(DWORD)] = static_cast<DWORD>(value + ri->getSymbol()->getAddress().get());
|
||||
}
|
||||
}
|
||||
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
// Use s_memory as a BASE, compute the difference
|
||||
const u64 code_diff = (u64)m_mem.m_code_addr - (u64)s_memory;
|
||||
|
||||
// Fix RUNTIME_FUNCTION records (.pdata section)
|
||||
for (auto& rf : rfs)
|
||||
{
|
||||
rf.BeginAddress += static_cast<DWORD>(code_diff);
|
||||
rf.EndAddress += static_cast<DWORD>(code_diff);
|
||||
}
|
||||
|
||||
s_unwater.emplace_back(std::move(rfs));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class
|
||||
class ObjectCache final : public llvm::ObjectCache
|
||||
{
|
||||
const std::string& m_path;
|
||||
|
||||
public:
|
||||
ObjectCache(const std::string& path)
|
||||
: m_path(path)
|
||||
{
|
||||
}
|
||||
|
||||
~ObjectCache() override = default;
|
||||
|
||||
void notifyObjectCompiled(const llvm::Module* module, llvm::MemoryBufferRef obj) override
|
||||
{
|
||||
std::string name = m_path;
|
||||
name.append(module->getName());
|
||||
fs::file(name, fs::rewrite).write(obj.getBufferStart(), obj.getBufferSize());
|
||||
LOG_SUCCESS(GENERAL, "LLVM: Created module: %s", module->getName().data());
|
||||
}
|
||||
|
||||
static std::unique_ptr<llvm::MemoryBuffer> load(const std::string& path)
|
||||
{
|
||||
if (fs::file cached{path, fs::read})
|
||||
{
|
||||
auto buf = llvm::MemoryBuffer::getNewUninitMemBuffer(cached.size());
|
||||
cached.read(const_cast<char*>(buf->getBufferStart()), buf->getBufferSize());
|
||||
return buf;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<llvm::MemoryBuffer> getObject(const llvm::Module* module) override
|
||||
{
|
||||
std::string path = m_path;
|
||||
path.append(module->getName());
|
||||
|
||||
if (auto buf = load(path))
|
||||
{
|
||||
LOG_SUCCESS(GENERAL, "LLVM: Loaded module: %s", module->getName().data());
|
||||
return buf;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, std::string _cpu)
|
||||
: m_link(_link)
|
||||
, m_cpu(std::move(_cpu))
|
||||
{
|
||||
if (m_cpu.empty())
|
||||
{
|
||||
m_cpu = llvm::sys::getHostCPUName();
|
||||
|
||||
if (m_cpu == "sandybridge" ||
|
||||
m_cpu == "ivybridge" ||
|
||||
m_cpu == "haswell" ||
|
||||
m_cpu == "broadwell" ||
|
||||
m_cpu == "skylake" ||
|
||||
m_cpu == "skylake-avx512" ||
|
||||
m_cpu == "cannonlake")
|
||||
{
|
||||
if (!utils::has_avx())
|
||||
{
|
||||
m_cpu = "nehalem";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
if (m_link.empty())
|
||||
{
|
||||
// Auxiliary JIT (does not use custom memory manager, only writes the objects)
|
||||
m_engine.reset(llvm::EngineBuilder(std::make_unique<llvm::Module>("null_", m_context))
|
||||
.setErrorStr(&result)
|
||||
.setOptLevel(llvm::CodeGenOpt::Aggressive)
|
||||
.setCodeModel(llvm::CodeModel::Small)
|
||||
.setMCPU(m_cpu)
|
||||
.create());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Primary JIT
|
||||
auto mem = std::make_unique<MemoryManager>(m_link);
|
||||
m_jit_el = std::make_unique<EventListener>(*mem);
|
||||
|
||||
m_engine.reset(llvm::EngineBuilder(std::make_unique<llvm::Module>("null", m_context))
|
||||
.setErrorStr(&result)
|
||||
.setMCJITMemoryManager(std::move(mem))
|
||||
.setOptLevel(llvm::CodeGenOpt::Aggressive)
|
||||
.setCodeModel(llvm::CodeModel::Small)
|
||||
.setMCPU(m_cpu)
|
||||
.create());
|
||||
|
||||
if (m_engine)
|
||||
{
|
||||
m_engine->RegisterJITEventListener(m_jit_el.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_engine)
|
||||
{
|
||||
fmt::throw_exception("LLVM: Failed to create ExecutionEngine: %s", result);
|
||||
}
|
||||
}
|
||||
|
||||
jit_compiler::~jit_compiler()
|
||||
{
|
||||
}
|
||||
|
||||
void jit_compiler::add(std::unique_ptr<llvm::Module> module, const std::string& path)
|
||||
{
|
||||
ObjectCache cache{path};
|
||||
m_engine->setObjectCache(&cache);
|
||||
|
||||
const auto ptr = module.get();
|
||||
m_engine->addModule(std::move(module));
|
||||
m_engine->generateCodeForModule(ptr);
|
||||
m_engine->setObjectCache(nullptr);
|
||||
|
||||
for (auto& func : ptr->functions())
|
||||
{
|
||||
// Delete IR to lower memory consumption
|
||||
func.deleteBody();
|
||||
}
|
||||
}
|
||||
|
||||
void jit_compiler::add(const std::string& path)
|
||||
{
|
||||
m_engine->addObjectFile(std::move(llvm::object::ObjectFile::createObjectFile(*ObjectCache::load(path)).get()));
|
||||
}
|
||||
|
||||
void jit_compiler::fin()
|
||||
{
|
||||
m_engine->finalizeObject();
|
||||
}
|
||||
|
||||
u64 jit_compiler::get(const std::string& name)
|
||||
{
|
||||
return m_engine->getGlobalValueAddress(name);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, u64> jit_compiler::add(std::unordered_map<std::string, std::string> data)
|
||||
{
|
||||
// Lock memory manager
|
||||
writer_lock lock(s_mutex);
|
||||
|
||||
std::unordered_map<std::string, u64> result;
|
||||
|
||||
std::size_t size = 0;
|
||||
|
||||
for (auto&& pair : data)
|
||||
{
|
||||
size += ::align(pair.second.size(), 16);
|
||||
}
|
||||
|
||||
utils::memory_commit(s_next, size, utils::protection::wx);
|
||||
std::memset(s_next, 0xc3, ::align(size, 4096));
|
||||
|
||||
for (auto&& pair : data)
|
||||
{
|
||||
std::memcpy(s_next, pair.second.data(), pair.second.size());
|
||||
result.emplace(pair.first, (u64)s_next);
|
||||
s_next = (void*)::align((u64)s_next + pair.second.size(), 16);
|
||||
}
|
||||
|
||||
s_next = (void*)::align((u64)s_next, 4096);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef LLVM_AVAILABLE
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "types.h"
|
||||
#include "mutex.h"
|
||||
|
||||
#include "restore_new.h"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push, 0)
|
||||
#endif
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/ExecutionEngine/ExecutionEngine.h"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
#include "define_new_memleakdetect.h"
|
||||
|
||||
// Temporary compiler interface
|
||||
class jit_compiler final
|
||||
{
|
||||
// Local LLVM context
|
||||
llvm::LLVMContext m_context;
|
||||
|
||||
// JIT Event Listener
|
||||
std::unique_ptr<struct EventListener> m_jit_el;
|
||||
|
||||
// Execution instance
|
||||
std::unique_ptr<llvm::ExecutionEngine> m_engine;
|
||||
|
||||
// Link table
|
||||
std::unordered_map<std::string, u64> m_link;
|
||||
|
||||
// Arch
|
||||
std::string m_cpu;
|
||||
|
||||
public:
|
||||
jit_compiler(const std::unordered_map<std::string, u64>& _link, std::string _cpu);
|
||||
~jit_compiler();
|
||||
|
||||
// Get LLVM context
|
||||
auto& get_context()
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
// Add module (path to obj cache dir)
|
||||
void add(std::unique_ptr<llvm::Module> module, const std::string& path);
|
||||
|
||||
// Add object (path to obj file)
|
||||
void add(const std::string& path);
|
||||
|
||||
// Finalize
|
||||
void fin();
|
||||
|
||||
// Get compiled function address
|
||||
u64 get(const std::string& name);
|
||||
|
||||
// Add functions directly to the memory manager (name -> code)
|
||||
static std::unordered_map<std::string, u64> add(std::unordered_map<std::string, std::string>);
|
||||
|
||||
// Get CPU info
|
||||
const std::string& cpu() const
|
||||
{
|
||||
return m_cpu;
|
||||
}
|
||||
|
||||
// Check JIT purpose
|
||||
bool is_primary() const
|
||||
{
|
||||
return !m_link.empty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "LUrlParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
|
||||
// check if the scheme name is valid
|
||||
static bool IsSchemeValid( const std::string& SchemeName )
|
||||
{
|
||||
for ( auto c : SchemeName )
|
||||
{
|
||||
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
|
||||
{
|
||||
if ( !IsValid() ) { return false; }
|
||||
|
||||
int Port = atoi( m_Port.c_str() );
|
||||
|
||||
if ( Port <= 0 || Port > 65535 ) { return false; }
|
||||
|
||||
if ( OutPort ) { *OutPort = Port; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// based on RFC 1738 and RFC 3986
|
||||
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
|
||||
{
|
||||
LUrlParser::clParseURL Result;
|
||||
|
||||
const char* CurrentString = URL.c_str();
|
||||
|
||||
/*
|
||||
* <scheme>:<scheme-specific-part>
|
||||
* <scheme> := [a-z\+\-\.]+
|
||||
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
|
||||
*/
|
||||
|
||||
// try to read scheme
|
||||
{
|
||||
const char* LocalString = strchr( CurrentString, ':' );
|
||||
|
||||
if ( !LocalString )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoUrlCharacter );
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
if ( !IsSchemeValid( Result.m_Scheme ) )
|
||||
{
|
||||
return clParseURL( LUrlParserError_InvalidSchemeName );
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
|
||||
|
||||
// skip ':'
|
||||
CurrentString = LocalString+1;
|
||||
}
|
||||
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
// skip "//"
|
||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char* LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString )
|
||||
{
|
||||
if ( *LocalString == '@' )
|
||||
{
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if ( *LocalString == '/' )
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
|
||||
if ( bHasUserName )
|
||||
{
|
||||
// read user name
|
||||
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
|
||||
|
||||
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
|
||||
if ( *CurrentString == ':' )
|
||||
{
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '@' ) LocalString++;
|
||||
|
||||
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if ( *CurrentString != '@' )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoAtSign );
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = ( *CurrentString == '[' );
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString )
|
||||
{
|
||||
if ( bHasBracket && *LocalString == ']' )
|
||||
{
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if ( *CurrentString == ':' )
|
||||
{
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '/' ) LocalString++;
|
||||
|
||||
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// end of string
|
||||
if ( !*CurrentString )
|
||||
{
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if ( *CurrentString != '/' )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoSlash );
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
|
||||
|
||||
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// check for query
|
||||
if ( *CurrentString == '?' )
|
||||
{
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '#' ) LocalString++;
|
||||
|
||||
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if ( *CurrentString == '#' )
|
||||
{
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString ) LocalString++;
|
||||
|
||||
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace LUrlParser
|
||||
{
|
||||
enum LUrlParserError
|
||||
{
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL()
|
||||
: m_ErrorCode( LUrlParserError_Uninitialized )
|
||||
{}
|
||||
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
|
||||
|
||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
|
||||
bool GetPort( int* OutPort ) const;
|
||||
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL( const std::string& URL );
|
||||
|
||||
private:
|
||||
explicit clParseURL( LUrlParserError ErrorCode )
|
||||
: m_ErrorCode( ErrorCode )
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace LUrlParser
|
||||
+98
-571
@@ -1,623 +1,150 @@
|
||||
#include "Log.h"
|
||||
#include "stdafx.h"
|
||||
#include "Thread.h"
|
||||
#include "File.h"
|
||||
#include "StrFmt.h"
|
||||
#include "sema.h"
|
||||
|
||||
#include "Utilities/sysinfo.h"
|
||||
#include "Utilities/Thread.h"
|
||||
#include "rpcs3_version.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
#include "Log.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
static std::string empty_string()
|
||||
namespace _log
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Thread-specific log prefix provider
|
||||
thread_local std::string(*g_tls_log_prefix)() = &empty_string;
|
||||
|
||||
template<>
|
||||
void fmt_class_string<logs::level>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto lev)
|
||||
logger& get_logger()
|
||||
{
|
||||
switch (lev)
|
||||
{
|
||||
case logs::level::always: return "Nothing";
|
||||
case logs::level::fatal: return "Fatal";
|
||||
case logs::level::error: return "Error";
|
||||
case logs::level::todo: return "TODO";
|
||||
case logs::level::success: return "Success";
|
||||
case logs::level::warning: return "Warning";
|
||||
case logs::level::notice: return "Notice";
|
||||
case logs::level::trace: return "Trace";
|
||||
case logs::level::_uninit: return unknown;
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
namespace logs
|
||||
{
|
||||
// Memory-mapped buffer size
|
||||
constexpr u64 s_log_size = 32 * 1024 * 1024;
|
||||
|
||||
class file_writer
|
||||
{
|
||||
fs::file m_file;
|
||||
std::string m_name;
|
||||
std::thread m_writer;
|
||||
fs::file m_fout;
|
||||
fs::file m_fout2;
|
||||
u64 m_max_size;
|
||||
|
||||
#ifdef _WIN32
|
||||
::HANDLE m_fmap;
|
||||
#endif
|
||||
uchar* m_fptr{};
|
||||
z_stream m_zs{};
|
||||
semaphore<> m_m;
|
||||
|
||||
alignas(128) atomic_t<u64> m_buf{0}; // MSB (40 bit): push begin, LSB (24 bis): push size
|
||||
alignas(128) atomic_t<u64> m_out{0}; // Amount of bytes written to file
|
||||
|
||||
uchar m_zout[65536];
|
||||
|
||||
// Write buffered logs immediately
|
||||
bool flush(u64 bufv);
|
||||
|
||||
public:
|
||||
file_writer(const std::string& name);
|
||||
|
||||
virtual ~file_writer();
|
||||
|
||||
// Append raw data
|
||||
void log(logs::level sev, const char* text, std::size_t size);
|
||||
};
|
||||
|
||||
struct channel_info
|
||||
{
|
||||
channel* pointer = nullptr;
|
||||
level enabled = level::notice;
|
||||
|
||||
void set_level(level value)
|
||||
{
|
||||
enabled = value;
|
||||
|
||||
if (pointer)
|
||||
{
|
||||
pointer->enabled = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct stored_message
|
||||
{
|
||||
message m;
|
||||
u64 stamp;
|
||||
std::string prefix;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct file_listener : public file_writer, public listener
|
||||
{
|
||||
file_listener(const std::string& name);
|
||||
|
||||
virtual ~file_listener() = default;
|
||||
|
||||
// Encode level, current thread name, channel name and write log message
|
||||
virtual void log(u64 stamp, const message& msg, const std::string& prefix, const std::string& text) override;
|
||||
|
||||
// Channel registry
|
||||
std::unordered_map<std::string, channel_info> channels;
|
||||
|
||||
// Messages for delayed listener initialization
|
||||
std::vector<stored_message> messages;
|
||||
};
|
||||
|
||||
static file_listener* get_logger()
|
||||
{
|
||||
// Use magic static
|
||||
static file_listener logger("RPCS3");
|
||||
return &logger;
|
||||
// Use magic static for global logger instance
|
||||
static logger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
static u64 get_stamp()
|
||||
{
|
||||
static struct time_initializer
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER freq;
|
||||
LARGE_INTEGER start;
|
||||
file_listener g_log_file(_PRGNAME_ ".log");
|
||||
|
||||
time_initializer()
|
||||
{
|
||||
QueryPerformanceFrequency(&freq);
|
||||
QueryPerformanceCounter(&start);
|
||||
}
|
||||
#else
|
||||
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
|
||||
#endif
|
||||
file_writer g_tty_file("TTY.log");
|
||||
|
||||
u64 get() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
const LONGLONG diff = now.QuadPart - start.QuadPart;
|
||||
return diff / freq.QuadPart * 1'000'000 + diff % freq.QuadPart * 1'000'000 / freq.QuadPart;
|
||||
#else
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
|
||||
#endif
|
||||
}
|
||||
} timebase{};
|
||||
|
||||
return timebase.get();
|
||||
}
|
||||
|
||||
channel GENERAL("");
|
||||
channel LOADER("LDR");
|
||||
channel MEMORY("MEM");
|
||||
channel RSX("RSX");
|
||||
channel HLE("HLE");
|
||||
channel PPU("PPU");
|
||||
channel SPU("SPU");
|
||||
channel GENERAL("", level::notice);
|
||||
channel LOADER("LDR", level::notice);
|
||||
channel MEMORY("MEM", level::notice);
|
||||
channel RSX("RSX", level::notice);
|
||||
channel HLE("HLE", level::notice);
|
||||
channel PPU("PPU", level::notice);
|
||||
channel SPU("SPU", level::notice);
|
||||
channel ARMv7("ARMv7");
|
||||
}
|
||||
|
||||
// Channel registry mutex
|
||||
semaphore<> g_mutex;
|
||||
_log::listener::listener()
|
||||
{
|
||||
// Register self
|
||||
get_logger().add_listener(this);
|
||||
}
|
||||
|
||||
// Must be set to true in main()
|
||||
atomic_t<bool> g_init{false};
|
||||
_log::listener::~listener()
|
||||
{
|
||||
// Unregister self
|
||||
get_logger().remove_listener(this);
|
||||
}
|
||||
|
||||
void reset()
|
||||
_log::channel::channel(const std::string& name, _log::level init_level)
|
||||
: name{ name }
|
||||
, enabled{ init_level }
|
||||
{
|
||||
// TODO: register config property "name" associated with "enabled" member
|
||||
}
|
||||
|
||||
void _log::logger::add_listener(_log::listener* listener)
|
||||
{
|
||||
std::lock_guard<shared_mutex> lock(m_mutex);
|
||||
|
||||
m_listeners.emplace(listener);
|
||||
}
|
||||
|
||||
void _log::logger::remove_listener(_log::listener* listener)
|
||||
{
|
||||
std::lock_guard<shared_mutex> lock(m_mutex);
|
||||
|
||||
m_listeners.erase(listener);
|
||||
}
|
||||
|
||||
void _log::logger::broadcast(const _log::channel& ch, _log::level sev, const std::string& text) const
|
||||
{
|
||||
reader_lock lock(m_mutex);
|
||||
|
||||
for (auto listener : m_listeners)
|
||||
{
|
||||
semaphore_lock lock(g_mutex);
|
||||
|
||||
for (auto&& pair : get_logger()->channels)
|
||||
{
|
||||
pair.second.set_level(level::notice);
|
||||
}
|
||||
}
|
||||
|
||||
void set_level(const std::string& ch_name, level value)
|
||||
{
|
||||
semaphore_lock lock(g_mutex);
|
||||
|
||||
get_logger()->channels[ch_name].set_level(value);
|
||||
}
|
||||
|
||||
// Must be called in main() to stop accumulating messages in g_messages
|
||||
void set_init()
|
||||
{
|
||||
if (!g_init)
|
||||
{
|
||||
semaphore_lock lock(g_mutex);
|
||||
get_logger()->messages.clear();
|
||||
g_init = true;
|
||||
}
|
||||
listener->log(ch, sev, text);
|
||||
}
|
||||
}
|
||||
|
||||
logs::listener::~listener()
|
||||
void _log::broadcast(const _log::channel& ch, _log::level sev, const std::string& text)
|
||||
{
|
||||
get_logger().broadcast(ch, sev, text);
|
||||
}
|
||||
|
||||
void logs::listener::add(logs::listener* _new)
|
||||
_log::file_writer::file_writer(const std::string& name)
|
||||
{
|
||||
// Get first (main) listener
|
||||
listener* lis = get_logger();
|
||||
|
||||
semaphore_lock lock(g_mutex);
|
||||
|
||||
// Install new listener at the end of linked list
|
||||
while (lis->m_next || !lis->m_next.compare_and_swap_test(nullptr, _new))
|
||||
{
|
||||
lis = lis->m_next;
|
||||
}
|
||||
|
||||
// Send initial messages
|
||||
for (const auto& msg : get_logger()->messages)
|
||||
{
|
||||
_new->log(msg.stamp, msg.m, msg.prefix, msg.text);
|
||||
}
|
||||
}
|
||||
|
||||
void logs::message::broadcast(const char* fmt, const fmt_type_info* sup, const u64* args)
|
||||
{
|
||||
// Get timestamp
|
||||
const u64 stamp = get_stamp();
|
||||
|
||||
// Register channel
|
||||
if (ch->enabled == level::_uninit)
|
||||
{
|
||||
semaphore_lock lock(g_mutex);
|
||||
|
||||
auto& info = get_logger()->channels[ch->name];
|
||||
|
||||
if (info.pointer && info.pointer != ch)
|
||||
{
|
||||
fmt::throw_exception("logs::channel repetition: %s", ch->name);
|
||||
}
|
||||
else if (!info.pointer)
|
||||
{
|
||||
info.pointer = ch;
|
||||
ch->enabled = info.enabled;
|
||||
|
||||
// Check level again
|
||||
if (info.enabled < sev)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get text
|
||||
thread_local std::string text; text.clear();
|
||||
fmt::raw_append(text, fmt, sup, args);
|
||||
std::string prefix = g_tls_log_prefix();
|
||||
|
||||
// Get first (main) listener
|
||||
listener* lis = get_logger();
|
||||
|
||||
if (!g_init)
|
||||
{
|
||||
semaphore_lock lock(g_mutex);
|
||||
|
||||
if (!g_init)
|
||||
{
|
||||
while (lis)
|
||||
{
|
||||
lis->log(stamp, *this, prefix, text);
|
||||
lis = lis->m_next;
|
||||
}
|
||||
|
||||
// Store message additionally
|
||||
get_logger()->messages.emplace_back(stored_message{*this, stamp, std::move(prefix), text});
|
||||
}
|
||||
}
|
||||
|
||||
// Send message to all listeners
|
||||
while (lis)
|
||||
{
|
||||
lis->log(stamp, *this, prefix, text);
|
||||
lis = lis->m_next;
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] extern void catch_all_exceptions();
|
||||
|
||||
logs::file_writer::file_writer(const std::string& name)
|
||||
: m_name(name)
|
||||
{
|
||||
const std::string log_name = fs::get_config_dir() + name + ".log";
|
||||
const std::string buf_name = fs::get_config_dir() + name + ".buf";
|
||||
|
||||
try
|
||||
{
|
||||
if (!m_file.open(buf_name, fs::read + fs::write + fs::create + fs::trunc + fs::unshare))
|
||||
if (!m_file.open(fs::get_config_dir() + name, fom::rewrite | fom::append))
|
||||
{
|
||||
if (fs::g_tls_error == fs::error::acces)
|
||||
{
|
||||
if (fs::exists(buf_name))
|
||||
{
|
||||
fmt::throw_exception("Another instance of %s is running. Close it or kill its process, if necessary.", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::throw_exception("Cannot create %s.log (access denied)."
|
||||
throw EXCEPTION("Can't create log file %s (error %d)", name, errno);
|
||||
}
|
||||
}
|
||||
catch (const fmt::exception& e)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
"\nNote that %s cannot be installed in Program Files or similar directory with limited permissions."
|
||||
MessageBoxA(0, e.what(), "_log::file_writer() failed", MB_ICONERROR);
|
||||
#else
|
||||
"\nPlease, check %s permissions in '~/.config/'."
|
||||
std::printf("_log::file_writer() failed: %s\n", e.what());
|
||||
#endif
|
||||
, name, name);
|
||||
}
|
||||
}
|
||||
|
||||
fmt::throw_exception("Cannot create %s.log (error %s)", name, fs::g_tls_error);
|
||||
}
|
||||
|
||||
// Check free space
|
||||
fs::device_stat stats{};
|
||||
if (!fs::statfs(fs::get_config_dir(), stats) || stats.avail_free < s_log_size * 8)
|
||||
{
|
||||
fmt::throw_exception("Not enough free space (%f KB)", stats.avail_free / 1000000.);
|
||||
}
|
||||
|
||||
// Limit log size to ~25% of free space
|
||||
m_max_size = stats.avail_free / 4;
|
||||
|
||||
// Initialize memory mapped file
|
||||
#ifdef _WIN32
|
||||
m_fmap = CreateFileMappingW(m_file.get_handle(), 0, PAGE_READWRITE, s_log_size >> 32, s_log_size & 0xffffffff, 0);
|
||||
m_fptr = m_fmap ? (uchar*)MapViewOfFile(m_fmap, FILE_MAP_WRITE, 0, 0, 0) : nullptr;
|
||||
#else
|
||||
m_file.trunc(s_log_size);
|
||||
m_fptr = (uchar*)::mmap(0, s_log_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_file.get_handle(), 0);
|
||||
#endif
|
||||
|
||||
verify(name.c_str()), m_fptr;
|
||||
|
||||
// Rotate backups (TODO)
|
||||
fs::remove_file(fs::get_config_dir() + name + "1.log.gz");
|
||||
fs::create_dir(fs::get_config_dir() + "old_logs");
|
||||
fs::rename(fs::get_config_dir() + m_name + ".log.gz", fs::get_config_dir() + "old_logs/" + m_name + ".log.gz", true);
|
||||
|
||||
// Actual log file (allowed to fail)
|
||||
m_fout.open(log_name, fs::rewrite);
|
||||
|
||||
// Compressed log
|
||||
if (!m_fout2.open(log_name + ".gz", fs::rewrite) || deflateInit2(&m_zs, 9, Z_DEFLATED, 16 + 15, 9, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
{
|
||||
m_fout2.close();
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::thread([text = std::string{e.what()}]{ report_fatal_error(text); }).detach();
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::thread([]{ report_fatal_error("Unknown error" HERE); }).detach();
|
||||
return;
|
||||
}
|
||||
|
||||
m_writer = std::thread([this]()
|
||||
{
|
||||
thread_ctrl::set_native_priority(-1);
|
||||
|
||||
while (true)
|
||||
{
|
||||
const u64 bufv = m_buf;
|
||||
|
||||
if (bufv & 0xffffff)
|
||||
{
|
||||
// Wait if threads are writing logs
|
||||
std::this_thread::yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flush(bufv))
|
||||
{
|
||||
if (m_out == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logs::file_writer::~file_writer()
|
||||
{
|
||||
if (!m_fptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop writer thread
|
||||
while (m_out << 24 < m_buf)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
m_out = -1;
|
||||
m_writer.join();
|
||||
|
||||
if (m_fout2)
|
||||
{
|
||||
m_zs.avail_in = 0;
|
||||
m_zs.next_in = nullptr;
|
||||
|
||||
do
|
||||
{
|
||||
m_zs.avail_out = sizeof(m_zout);
|
||||
m_zs.next_out = m_zout;
|
||||
|
||||
if (deflate(&m_zs, Z_FINISH) == Z_STREAM_ERROR || m_fout2.write(m_zout, sizeof(m_zout) - m_zs.avail_out) != sizeof(m_zout) - m_zs.avail_out)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (m_zs.avail_out == 0);
|
||||
|
||||
deflateEnd(&m_zs);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
UnmapViewOfFile(m_fptr);
|
||||
CloseHandle(m_fmap);
|
||||
#else
|
||||
::munmap(m_fptr, s_log_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool logs::file_writer::flush(u64 bufv)
|
||||
{
|
||||
semaphore_lock lock(m_m);
|
||||
|
||||
const u64 st = +m_out;
|
||||
const u64 end = std::min<u64>((st + s_log_size) & ~(s_log_size - 1), bufv >> 24);
|
||||
|
||||
if (end > st)
|
||||
{
|
||||
// Avoid writing too big fragments
|
||||
const u64 size = std::min<u64>(end - st, sizeof(m_zout) / 2);
|
||||
|
||||
// Write uncompressed
|
||||
if (m_fout && st < m_max_size && m_fout.write(m_fptr + st % s_log_size, size) != size)
|
||||
{
|
||||
m_fout.close();
|
||||
}
|
||||
|
||||
// Write compressed
|
||||
if (m_fout2 && st < m_max_size)
|
||||
{
|
||||
m_zs.avail_in = size;
|
||||
m_zs.next_in = m_fptr + st % s_log_size;
|
||||
|
||||
do
|
||||
{
|
||||
m_zs.avail_out = sizeof(m_zout);
|
||||
m_zs.next_out = m_zout;
|
||||
|
||||
if (deflate(&m_zs, Z_NO_FLUSH) == Z_STREAM_ERROR || m_fout2.write(m_zout, sizeof(m_zout) - m_zs.avail_out) != sizeof(m_zout) - m_zs.avail_out)
|
||||
{
|
||||
deflateEnd(&m_zs);
|
||||
m_fout2.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (m_zs.avail_out == 0);
|
||||
}
|
||||
|
||||
m_out += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void logs::file_writer::log(logs::level sev, const char* text, std::size_t size)
|
||||
{
|
||||
if (!m_fptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: write bigger fragment directly in blocking manner
|
||||
while (size && size <= 0xffffff)
|
||||
{
|
||||
u64 bufv;
|
||||
|
||||
const auto pos = m_buf.atomic_op([&](u64& v) -> uchar*
|
||||
{
|
||||
const u64 v1 = v >> 24;
|
||||
const u64 v2 = v & 0xffffff;
|
||||
|
||||
if (UNLIKELY(v2 + size > 0xffffff || v1 + v2 + size >= m_out + s_log_size))
|
||||
{
|
||||
bufv = v;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
v += size;
|
||||
return m_fptr + (v1 + v2) % s_log_size;
|
||||
});
|
||||
|
||||
if (UNLIKELY(!pos))
|
||||
{
|
||||
if ((bufv & 0xffffff) + size > 0xffffff || bufv & 0xffffff)
|
||||
{
|
||||
// Concurrency limit reached
|
||||
std::this_thread::yield();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue is full, need to write out
|
||||
flush(bufv);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pos + size > m_fptr + s_log_size)
|
||||
{
|
||||
const auto frag = m_fptr + s_log_size - pos;
|
||||
std::memcpy(pos, text, frag);
|
||||
std::memcpy(m_fptr, text + frag, size - frag);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(pos, text, size);
|
||||
}
|
||||
|
||||
m_buf += (u64{size} << 24) - size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logs::file_listener::file_listener(const std::string& name)
|
||||
: file_writer(name)
|
||||
, listener()
|
||||
void _log::file_writer::log(const std::string& text)
|
||||
{
|
||||
// Write UTF-8 BOM
|
||||
file_writer::log(logs::level::always, "\xEF\xBB\xBF", 3);
|
||||
|
||||
// Write initial message
|
||||
stored_message ver;
|
||||
ver.m.ch = nullptr;
|
||||
ver.m.sev = level::always;
|
||||
ver.stamp = 0;
|
||||
ver.text = fmt::format("RPCS3 v%s | %s\n%s", rpcs3::version.to_string(), rpcs3::get_branch(), utils::get_system_info());
|
||||
|
||||
file_writer::log(logs::level::always, ver.text.data(), ver.text.size());
|
||||
file_writer::log(logs::level::always, "\n", 1);
|
||||
messages.emplace_back(std::move(ver));
|
||||
m_file.write(text);
|
||||
}
|
||||
|
||||
void logs::file_listener::log(u64 stamp, const logs::message& msg, const std::string& prefix, const std::string& _text)
|
||||
std::size_t _log::file_writer::size() const
|
||||
{
|
||||
thread_local std::string text;
|
||||
return m_file.seek(0, fs::seek_cur);
|
||||
}
|
||||
|
||||
void _log::file_listener::log(const _log::channel& ch, _log::level sev, const std::string& text)
|
||||
{
|
||||
std::string msg; msg.reserve(text.size() + 200);
|
||||
|
||||
// Used character: U+00B7 (Middle Dot)
|
||||
switch (msg.sev)
|
||||
switch (sev)
|
||||
{
|
||||
case level::always: text = u8"·A "; break;
|
||||
case level::fatal: text = u8"·F "; break;
|
||||
case level::error: text = u8"·E "; break;
|
||||
case level::todo: text = u8"·U "; break;
|
||||
case level::success: text = u8"·S "; break;
|
||||
case level::warning: text = u8"·W "; break;
|
||||
case level::notice: text = u8"·! "; break;
|
||||
case level::trace: text = u8"·T "; break;
|
||||
case level::_uninit: text = u8"· "; break;
|
||||
case level::always: msg = u8"·A "; break;
|
||||
case level::fatal: msg = u8"·F "; break;
|
||||
case level::error: msg = u8"·E "; break;
|
||||
case level::todo: msg = u8"·U "; break;
|
||||
case level::success: msg = u8"·S "; break;
|
||||
case level::warning: msg = u8"·W "; break;
|
||||
case level::notice: msg = u8"·! "; break;
|
||||
case level::trace: msg = u8"·T "; break;
|
||||
}
|
||||
|
||||
// Print miscosecond timestamp
|
||||
const u64 hours = stamp / 3600'000'000;
|
||||
const u64 mins = (stamp % 3600'000'000) / 60'000'000;
|
||||
const u64 secs = (stamp % 60'000'000) / 1'000'000;
|
||||
const u64 frac = (stamp % 1'000'000);
|
||||
fmt::append(text, "%u:%02u:%02u.%06u ", hours, mins, secs, frac);
|
||||
// TODO: print time?
|
||||
|
||||
if (prefix.size() > 0)
|
||||
if (auto t = thread_ctrl::get_current())
|
||||
{
|
||||
text += "{";
|
||||
text += prefix;
|
||||
text += "} ";
|
||||
msg += '{';
|
||||
msg += t->get_name();
|
||||
msg += "} ";
|
||||
}
|
||||
|
||||
if (msg.ch && '\0' != *msg.ch->name)
|
||||
if (ch.name.size())
|
||||
{
|
||||
text += msg.ch->name;
|
||||
text += msg.sev == level::todo ? " TODO: " : ": ";
|
||||
msg += ch.name;
|
||||
msg += sev == level::todo ? " TODO: " : ": ";
|
||||
}
|
||||
else if (msg.sev == level::todo)
|
||||
else if (sev == level::todo)
|
||||
{
|
||||
text += "TODO: ";
|
||||
msg += "TODO: ";
|
||||
}
|
||||
|
||||
msg += text;
|
||||
msg += '\n';
|
||||
|
||||
text += _text;
|
||||
text += '\n';
|
||||
|
||||
file_writer::log(msg.sev, text.data(), text.size());
|
||||
file_writer::log(msg);
|
||||
}
|
||||
|
||||
+89
-58
@@ -1,85 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
#include "StrFmt.h"
|
||||
#include <climits>
|
||||
#include "SharedMutex.h"
|
||||
|
||||
namespace logs
|
||||
namespace _log
|
||||
{
|
||||
enum class level : uint
|
||||
{
|
||||
always, // Highest log severity (unused, cannot be disabled)
|
||||
always, // highest level (unused, cannot be disabled)
|
||||
fatal,
|
||||
error,
|
||||
todo,
|
||||
success,
|
||||
warning,
|
||||
notice,
|
||||
trace, // Lowest severity (usually disabled)
|
||||
|
||||
_uninit = UINT_MAX, // Special value for delayed initialization
|
||||
trace, // lowest level (usually disabled)
|
||||
};
|
||||
|
||||
struct channel;
|
||||
struct listener;
|
||||
|
||||
// Message information (temporary data)
|
||||
struct message
|
||||
// Log manager
|
||||
class logger final
|
||||
{
|
||||
channel* ch;
|
||||
level sev;
|
||||
mutable shared_mutex m_mutex;
|
||||
|
||||
// Send log message to global logger instance
|
||||
void broadcast(const char*, const fmt_type_info*, const u64*);
|
||||
};
|
||||
|
||||
class listener
|
||||
{
|
||||
// Next listener (linked list)
|
||||
atomic_t<listener*> m_next{};
|
||||
|
||||
friend struct message;
|
||||
std::set<listener*> m_listeners;
|
||||
|
||||
public:
|
||||
constexpr listener() = default;
|
||||
// Register listener
|
||||
void add_listener(listener* listener);
|
||||
|
||||
virtual ~listener();
|
||||
// Unregister listener
|
||||
void remove_listener(listener* listener);
|
||||
|
||||
// Process log message
|
||||
virtual void log(u64 stamp, const message& msg, const std::string& prefix, const std::string& text) = 0;
|
||||
|
||||
// Add new listener
|
||||
static void add(listener*);
|
||||
// Send log message to all listeners
|
||||
void broadcast(const channel& ch, level sev, const std::string& text) const;
|
||||
};
|
||||
|
||||
// Send log message to global logger instance
|
||||
void broadcast(const channel& ch, level sev, const std::string& text);
|
||||
|
||||
// Log channel (source)
|
||||
struct channel
|
||||
{
|
||||
// Channel prefix (added to every log message)
|
||||
const char* const name;
|
||||
// Channel prefix (also used for identification)
|
||||
const std::string name;
|
||||
|
||||
// The lowest logging level enabled for this channel (used for early filtering)
|
||||
atomic_t<level> enabled;
|
||||
std::atomic<level> enabled;
|
||||
|
||||
// Constant initialization: channel name
|
||||
constexpr channel(const char* name)
|
||||
: name(name)
|
||||
, enabled(level::_uninit)
|
||||
// Initialization (max level enabled by default)
|
||||
channel(const std::string& name, level = level::trace);
|
||||
|
||||
virtual ~channel() = default;
|
||||
|
||||
// Log without formatting
|
||||
force_inline void log(level sev, const std::string& text) const
|
||||
{
|
||||
if (sev <= enabled)
|
||||
broadcast(*this, sev, text);
|
||||
}
|
||||
|
||||
// Formatting function
|
||||
// Log with formatting
|
||||
template<typename... Args>
|
||||
SAFE_BUFFERS FORCE_INLINE void format(level sev, const char* fmt, const Args&... args)
|
||||
force_inline safe_buffers void format(level sev, const char* fmt, const Args&... args) const
|
||||
{
|
||||
if (UNLIKELY(sev <= enabled))
|
||||
{
|
||||
message{this, sev}.broadcast(fmt, fmt::get_type_info<fmt_unveil_t<Args>...>(), fmt_args_t<Args...>{fmt_unveil<Args>::get(args)...});
|
||||
}
|
||||
if (sev <= enabled)
|
||||
broadcast(*this, sev, fmt::format(fmt, fmt::do_unveil(args)...));
|
||||
}
|
||||
|
||||
#define GEN_LOG_METHOD(_sev)\
|
||||
template<typename... Args>\
|
||||
SAFE_BUFFERS void _sev(const char* fmt, const Args&... args)\
|
||||
force_inline void _sev(const char* fmt, const Args&... args)\
|
||||
{\
|
||||
return format<Args...>(level::_sev, fmt, args...);\
|
||||
}
|
||||
@@ -95,7 +87,52 @@ namespace logs
|
||||
#undef GEN_LOG_METHOD
|
||||
};
|
||||
|
||||
/* Small set of predefined channels */
|
||||
// Log listener (destination)
|
||||
struct listener
|
||||
{
|
||||
listener();
|
||||
|
||||
virtual ~listener();
|
||||
|
||||
virtual void log(const channel& ch, level sev, const std::string& text) = 0;
|
||||
};
|
||||
|
||||
class file_writer
|
||||
{
|
||||
// Could be memory-mapped file
|
||||
fs::file m_file;
|
||||
|
||||
public:
|
||||
file_writer(const std::string& name);
|
||||
|
||||
virtual ~file_writer() = default;
|
||||
|
||||
// Append raw data
|
||||
void log(const std::string& text);
|
||||
|
||||
// Get current file size (may be used by secondary readers)
|
||||
std::size_t size() const;
|
||||
};
|
||||
|
||||
struct file_listener : public file_writer, public listener
|
||||
{
|
||||
file_listener(const std::string& name)
|
||||
: file_writer(name)
|
||||
, listener()
|
||||
{
|
||||
}
|
||||
|
||||
// Encode level, current thread name, channel name and write log message
|
||||
virtual void log(const channel& ch, level sev, const std::string& text) override;
|
||||
};
|
||||
|
||||
// Global variable for RPCS3.log
|
||||
extern file_listener g_log_file;
|
||||
|
||||
// Global variable for TTY.log
|
||||
extern file_writer g_tty_file;
|
||||
|
||||
// Small set of predefined channels:
|
||||
|
||||
extern channel GENERAL;
|
||||
extern channel LOADER;
|
||||
@@ -105,20 +142,14 @@ namespace logs
|
||||
extern channel PPU;
|
||||
extern channel SPU;
|
||||
extern channel ARMv7;
|
||||
|
||||
// Log level control: set all channels to level::notice
|
||||
void reset();
|
||||
|
||||
// Log level control: register channel if necessary, set channel level
|
||||
void set_level(const std::string&, level);
|
||||
}
|
||||
|
||||
// Legacy:
|
||||
|
||||
#define LOG_SUCCESS(ch, fmt, ...) logs::ch.success("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_NOTICE(ch, fmt, ...) logs::ch.notice ("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARNING(ch, fmt, ...) logs::ch.warning("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(ch, fmt, ...) logs::ch.error ("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_TODO(ch, fmt, ...) logs::ch.todo ("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE(ch, fmt, ...) logs::ch.trace ("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_FATAL(ch, fmt, ...) logs::ch.fatal ("" fmt, ##__VA_ARGS__)
|
||||
#define LOG_SUCCESS(ch, fmt, ...) _log::ch.success(fmt, ##__VA_ARGS__)
|
||||
#define LOG_NOTICE(ch, fmt, ...) _log::ch.notice (fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARNING(ch, fmt, ...) _log::ch.warning(fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(ch, fmt, ...) _log::ch.error (fmt, ##__VA_ARGS__)
|
||||
#define LOG_TODO(ch, fmt, ...) _log::ch.todo (fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE(ch, fmt, ...) _log::ch.trace (fmt, ##__VA_ARGS__)
|
||||
#define LOG_FATAL(ch, fmt, ...) _log::ch.fatal (fmt, ##__VA_ARGS__)
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
class MTProgressDialog : public wxDialog
|
||||
{
|
||||
wxGauge** m_gauge;
|
||||
wxStaticText** m_msg;
|
||||
|
||||
wxArrayLong m_maximum;
|
||||
const u8 m_cores;
|
||||
|
||||
static const uint layout = 16;
|
||||
static const uint maxdial = 65536;
|
||||
wxArrayInt m_lastupdate;
|
||||
|
||||
public:
|
||||
MTProgressDialog(wxWindow* parent, const wxSize& size, const wxString& title,
|
||||
const wxString& msg, const wxArrayLong& maximum, const u8 cores)
|
||||
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition)
|
||||
, m_maximum(maximum)
|
||||
, m_cores(cores)
|
||||
{
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_gauge = new wxGauge*[m_cores];
|
||||
m_msg = new wxStaticText*[m_cores];
|
||||
|
||||
m_lastupdate.SetCount(cores);
|
||||
|
||||
for(uint i=0; i<m_cores; ++i)
|
||||
{
|
||||
m_lastupdate[i] = -1;
|
||||
|
||||
m_msg[i] = new wxStaticText(this, wxID_ANY, msg);
|
||||
sizer->Add(m_msg[i], 0, wxLEFT | wxTOP, layout);
|
||||
|
||||
m_gauge[i] = new wxGauge(this, wxID_ANY, maxdial,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxGA_HORIZONTAL );
|
||||
|
||||
sizer->Add(m_gauge[i], 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, layout);
|
||||
m_gauge[i]->SetValue(0);
|
||||
|
||||
sizer->AddSpacer(5);
|
||||
}
|
||||
|
||||
SetSizerAndFit(sizer);
|
||||
if(size != wxDefaultSize)
|
||||
{
|
||||
SetSize(size);
|
||||
}
|
||||
else
|
||||
{
|
||||
wxSize ws;
|
||||
ws.x = 400;
|
||||
ws.y = GetSize().y + 8;
|
||||
SetSize(ws);
|
||||
}
|
||||
|
||||
m_maximum.SetCount(m_cores);
|
||||
|
||||
Show();
|
||||
}
|
||||
|
||||
force_inline void Update(const u8 thread_id, const u64 value, const wxString& msg)
|
||||
{
|
||||
if(thread_id > m_cores) return;
|
||||
|
||||
const int curupdate = (int)(((double)value/(double)m_maximum[thread_id])*1000);
|
||||
if(curupdate == m_lastupdate[thread_id]) return;
|
||||
m_lastupdate[thread_id] = curupdate;
|
||||
|
||||
m_msg[thread_id]->SetLabel(msg);
|
||||
|
||||
if(value >= (u32)m_maximum[thread_id]) return;
|
||||
m_gauge[thread_id]->SetValue(((double)value / (double)m_maximum[thread_id]) * maxdial);
|
||||
}
|
||||
|
||||
const u32 GetMaxValue(const uint thread_id) const
|
||||
{
|
||||
if(thread_id > m_cores) return 0;
|
||||
return m_maximum[thread_id];
|
||||
}
|
||||
|
||||
void SetMaxFor(const uint thread_id, const u64 val)
|
||||
{
|
||||
if(thread_id > m_cores) return;
|
||||
m_maximum[thread_id] = val;
|
||||
m_lastupdate[thread_id] = 0;
|
||||
}
|
||||
|
||||
virtual void Close(bool force = false)
|
||||
{
|
||||
m_lastupdate.Empty();
|
||||
m_maximum.Empty();
|
||||
|
||||
wxDialog::Close(force);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "stdafx.h"
|
||||
#include "Utilities/Semaphore.h"
|
||||
|
||||
bool semaphore_t::try_wait()
|
||||
{
|
||||
// check m_value without interlocked op
|
||||
if (m_var.load().value == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to decrement m_value atomically
|
||||
const auto old = m_var.atomic_op([](sync_var_t& var)
|
||||
{
|
||||
if (var.value)
|
||||
{
|
||||
var.value--;
|
||||
}
|
||||
});
|
||||
|
||||
// recheck atomic result
|
||||
if (old.value == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool semaphore_t::try_post()
|
||||
{
|
||||
// check m_value without interlocked op
|
||||
if (m_var.load().value >= max_value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to increment m_value atomically
|
||||
const auto old = m_var.atomic_op([&](sync_var_t& var)
|
||||
{
|
||||
if (var.value < max_value)
|
||||
{
|
||||
var.value++;
|
||||
}
|
||||
});
|
||||
|
||||
// recheck atomic result
|
||||
if (old.value >= max_value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (old.waiters)
|
||||
{
|
||||
// notify waiting thread
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void semaphore_t::wait()
|
||||
{
|
||||
if (m_var.atomic_op([](sync_var_t& var) -> bool
|
||||
{
|
||||
if (var.value)
|
||||
{
|
||||
var.value--;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//var.waiters++;
|
||||
|
||||
return false;
|
||||
}
|
||||
}))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
m_var.atomic_op([](sync_var_t& var)
|
||||
{
|
||||
var.waiters++;
|
||||
});
|
||||
|
||||
while (!m_var.atomic_op([](sync_var_t& var) -> bool
|
||||
{
|
||||
if (var.value)
|
||||
{
|
||||
var.value--;
|
||||
var.waiters--;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}))
|
||||
{
|
||||
m_cv.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
bool semaphore_t::post_and_wait()
|
||||
{
|
||||
// TODO: merge these functions? Probably has a race condition.
|
||||
if (try_wait()) return false;
|
||||
|
||||
try_post();
|
||||
wait();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
class semaphore_t
|
||||
{
|
||||
// semaphore mutex
|
||||
std::mutex m_mutex;
|
||||
|
||||
// semaphore condition variable
|
||||
std::condition_variable m_cv;
|
||||
|
||||
struct sync_var_t
|
||||
{
|
||||
u32 value; // current semaphore value
|
||||
u32 waiters; // current amount of waiters
|
||||
};
|
||||
|
||||
// current semaphore value
|
||||
atomic_t<sync_var_t> m_var;
|
||||
|
||||
public:
|
||||
// max semaphore value
|
||||
const u32 max_value;
|
||||
|
||||
semaphore_t(u32 max_value = 1, u32 value = 0)
|
||||
: m_var(sync_var_t{ value, 0 })
|
||||
, max_value(max_value)
|
||||
{
|
||||
}
|
||||
|
||||
bool try_wait();
|
||||
|
||||
bool try_post();
|
||||
|
||||
void wait();
|
||||
|
||||
bool post_and_wait();
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "stdafx.h"
|
||||
#include "SharedMutex.h"
|
||||
|
||||
void shared_mutex::impl_lock_shared(u32 old_value)
|
||||
{
|
||||
// Throw if reader count breaks the "second" limit (it should be impossible)
|
||||
CHECK_ASSERTION((old_value & SM_READER_COUNT) != SM_READER_COUNT);
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
// Notify non-zero reader queue size
|
||||
m_ctrl |= SM_READER_QUEUE;
|
||||
|
||||
// Compensate incorrectly increased reader count
|
||||
if ((--m_ctrl & SM_READER_COUNT) == 0 && m_wq_size)
|
||||
{
|
||||
// Notify current exclusive owner (condition passed)
|
||||
m_ocv.notify_one();
|
||||
}
|
||||
|
||||
CHECK_ASSERTION(++m_rq_size);
|
||||
|
||||
// Obtain the reader lock
|
||||
while (!atomic_op(m_ctrl, op_lock_shared))
|
||||
{
|
||||
m_rcv.wait(lock);
|
||||
}
|
||||
|
||||
CHECK_ASSERTION(m_rq_size--);
|
||||
|
||||
if (m_rq_size == 0)
|
||||
{
|
||||
m_ctrl &= ~SM_READER_QUEUE;
|
||||
}
|
||||
}
|
||||
|
||||
void shared_mutex::impl_unlock_shared(u32 new_value)
|
||||
{
|
||||
// Throw if reader count was zero
|
||||
CHECK_ASSERTION((new_value & SM_READER_COUNT) != SM_READER_COUNT);
|
||||
|
||||
// Mutex cannot be unlocked before notification because m_ctrl has been changed outside
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_wq_size && (new_value & SM_READER_COUNT) == 0)
|
||||
{
|
||||
// Notify current exclusive owner that the latest reader is gone
|
||||
m_ocv.notify_one();
|
||||
}
|
||||
else if (m_rq_size)
|
||||
{
|
||||
m_rcv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void shared_mutex::impl_lock_excl(u32 value)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
// Notify non-zero writer queue size
|
||||
m_ctrl |= SM_WRITER_QUEUE;
|
||||
|
||||
CHECK_ASSERTION(++m_wq_size);
|
||||
|
||||
// Obtain the writer lock
|
||||
while (!atomic_op(m_ctrl, op_lock_excl))
|
||||
{
|
||||
m_wcv.wait(lock);
|
||||
}
|
||||
|
||||
// Wait for remaining readers
|
||||
while ((m_ctrl & SM_READER_COUNT) != 0)
|
||||
{
|
||||
m_ocv.wait(lock);
|
||||
}
|
||||
|
||||
CHECK_ASSERTION(m_wq_size--);
|
||||
|
||||
if (m_wq_size == 0)
|
||||
{
|
||||
m_ctrl &= ~SM_WRITER_QUEUE;
|
||||
}
|
||||
}
|
||||
|
||||
void shared_mutex::impl_unlock_excl(u32 value)
|
||||
{
|
||||
// Throw if was not locked exclusively
|
||||
CHECK_ASSERTION(value & SM_WRITER_LOCK);
|
||||
|
||||
// Mutex cannot be unlocked before notification because m_ctrl has been changed outside
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_wq_size)
|
||||
{
|
||||
// Notify next exclusive owner
|
||||
m_wcv.notify_one();
|
||||
}
|
||||
else if (m_rq_size)
|
||||
{
|
||||
// Notify all readers
|
||||
m_rcv.notify_all();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
//! An attempt to create effective implementation of "shared mutex", lock-free in optimistic case.
|
||||
//! All locking and unlocking may be done by single LOCK XADD or LOCK CMPXCHG instructions.
|
||||
//! MSVC implementation of std::shared_timed_mutex seems suboptimal.
|
||||
//! std::shared_mutex is not available until C++17.
|
||||
class shared_mutex final
|
||||
{
|
||||
enum : u32
|
||||
{
|
||||
SM_WRITER_LOCK = 1u << 31, // Exclusive lock flag, must be MSB
|
||||
SM_WRITER_QUEUE = 1u << 30, // Flag set if m_wq_size != 0
|
||||
SM_READER_QUEUE = 1u << 29, // Flag set if m_rq_size != 0
|
||||
|
||||
SM_READER_COUNT = SM_READER_QUEUE - 1, // Valid reader count bit mask
|
||||
SM_READER_MAX = 1u << 24, // Max reader count
|
||||
};
|
||||
|
||||
std::atomic<u32> m_ctrl{}; // Control atomic variable: reader count | SM_* flags
|
||||
std::thread::id m_owner{}; // Current exclusive owner (TODO: implement only for debug mode?)
|
||||
|
||||
std::mutex m_mutex;
|
||||
|
||||
u32 m_rq_size{}; // Reader queue size (threads waiting on m_rcv)
|
||||
u32 m_wq_size{}; // Writer queue size (threads waiting on m_wcv+m_ocv)
|
||||
|
||||
std::condition_variable m_rcv; // Reader queue
|
||||
std::condition_variable m_wcv; // Writer queue
|
||||
std::condition_variable m_ocv; // For current exclusive owner
|
||||
|
||||
static bool op_lock_shared(u32& ctrl)
|
||||
{
|
||||
// Check writer flags and reader limit
|
||||
return (ctrl & ~SM_READER_QUEUE) < SM_READER_MAX ? ctrl++, true : false;
|
||||
}
|
||||
|
||||
static bool op_lock_excl(u32& ctrl)
|
||||
{
|
||||
// Test and set writer lock
|
||||
return (ctrl & SM_WRITER_LOCK) == 0 ? ctrl |= SM_WRITER_LOCK, true : false;
|
||||
}
|
||||
|
||||
void impl_lock_shared(u32 old_ctrl);
|
||||
void impl_unlock_shared(u32 new_ctrl);
|
||||
void impl_lock_excl(u32 ctrl);
|
||||
void impl_unlock_excl(u32 ctrl);
|
||||
|
||||
public:
|
||||
shared_mutex() = default;
|
||||
|
||||
// Lock in shared mode
|
||||
void lock_shared()
|
||||
{
|
||||
const u32 old_ctrl = m_ctrl++;
|
||||
|
||||
// Check flags and reader limit
|
||||
if (old_ctrl >= SM_READER_MAX)
|
||||
{
|
||||
impl_lock_shared(old_ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to lock in shared mode
|
||||
bool try_lock_shared()
|
||||
{
|
||||
return atomic_op(m_ctrl, [](u32& ctrl)
|
||||
{
|
||||
// Check flags and reader limit
|
||||
return ctrl < SM_READER_MAX ? ctrl++, true : false;
|
||||
});
|
||||
}
|
||||
|
||||
// Unlock in shared mode
|
||||
void unlock_shared()
|
||||
{
|
||||
const u32 new_ctrl = --m_ctrl;
|
||||
|
||||
// Check if notification required
|
||||
if (new_ctrl >= SM_READER_MAX)
|
||||
{
|
||||
impl_unlock_shared(new_ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
// Lock exclusively
|
||||
void lock()
|
||||
{
|
||||
u32 value = 0;
|
||||
|
||||
if (!m_ctrl.compare_exchange_strong(value, SM_WRITER_LOCK))
|
||||
{
|
||||
impl_lock_excl(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to lock exclusively
|
||||
bool try_lock()
|
||||
{
|
||||
u32 value = 0;
|
||||
|
||||
return m_ctrl.compare_exchange_strong(value, SM_WRITER_LOCK);
|
||||
}
|
||||
|
||||
// Unlock exclusively
|
||||
void unlock()
|
||||
{
|
||||
const u32 value = m_ctrl.fetch_add(SM_WRITER_LOCK);
|
||||
|
||||
// Check if notification required
|
||||
if (value != SM_WRITER_LOCK)
|
||||
{
|
||||
impl_unlock_excl(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//! Simplified shared (reader) lock implementation, similar to std::lock_guard.
|
||||
//! std::shared_lock may be used instead if necessary.
|
||||
class reader_lock final
|
||||
{
|
||||
shared_mutex& m_mutex;
|
||||
|
||||
public:
|
||||
reader_lock(const reader_lock&) = delete;
|
||||
|
||||
reader_lock(shared_mutex& mutex)
|
||||
: m_mutex(mutex)
|
||||
{
|
||||
m_mutex.lock_shared();
|
||||
}
|
||||
|
||||
~reader_lock()
|
||||
{
|
||||
m_mutex.unlock_shared();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "stdafx.h"
|
||||
#include "Emu/CPU/CPUThread.h"
|
||||
|
||||
#include "SleepQueue.h"
|
||||
|
||||
void sleep_queue_entry_t::add_entry()
|
||||
{
|
||||
m_queue.emplace_back(std::static_pointer_cast<CPUThread>(m_thread.shared_from_this()));
|
||||
}
|
||||
|
||||
void sleep_queue_entry_t::remove_entry()
|
||||
{
|
||||
for (auto it = m_queue.begin(); it != m_queue.end(); it++)
|
||||
{
|
||||
if (it->get() == &m_thread)
|
||||
{
|
||||
m_queue.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sleep_queue_entry_t::find() const
|
||||
{
|
||||
for (auto it = m_queue.begin(); it != m_queue.end(); it++)
|
||||
{
|
||||
if (it->get() == &m_thread)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue)
|
||||
: m_thread(cpu)
|
||||
, m_queue(queue)
|
||||
{
|
||||
add_entry();
|
||||
cpu.sleep();
|
||||
}
|
||||
|
||||
sleep_queue_entry_t::sleep_queue_entry_t(sleep_entry_t& cpu, sleep_queue_t& queue, const defer_sleep_t&)
|
||||
: m_thread(cpu)
|
||||
, m_queue(queue)
|
||||
{
|
||||
cpu.sleep();
|
||||
}
|
||||
|
||||
sleep_queue_entry_t::~sleep_queue_entry_t()
|
||||
{
|
||||
remove_entry();
|
||||
m_thread.awake();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
using sleep_entry_t = class CPUThread;
|
||||
using sleep_queue_t = std::deque<std::shared_ptr<sleep_entry_t>>;
|
||||
|
||||
static struct defer_sleep_t {} const defer_sleep{};
|
||||
|
||||
// automatic object handling a thread entry in the sleep queue
|
||||
class sleep_queue_entry_t final
|
||||
{
|
||||
sleep_entry_t& m_thread;
|
||||
sleep_queue_t& m_queue;
|
||||
|
||||
void add_entry();
|
||||
void remove_entry();
|
||||
bool find() const;
|
||||
|
||||
public:
|
||||
// add specified thread to the sleep queue
|
||||
sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue);
|
||||
|
||||
// don't add specified thread to the sleep queue
|
||||
sleep_queue_entry_t(sleep_entry_t& entry, sleep_queue_t& queue, const defer_sleep_t&);
|
||||
|
||||
// removes specified thread from the sleep queue if added
|
||||
~sleep_queue_entry_t();
|
||||
|
||||
// add thread to the sleep queue
|
||||
void enter()
|
||||
{
|
||||
add_entry();
|
||||
}
|
||||
|
||||
// remove thread from the sleep queue
|
||||
void leave()
|
||||
{
|
||||
remove_entry();
|
||||
}
|
||||
|
||||
// check whether the thread exists in the sleep queue
|
||||
explicit operator bool() const
|
||||
{
|
||||
return find();
|
||||
}
|
||||
};
|
||||
+178
-293
@@ -1,307 +1,85 @@
|
||||
#include "StrFmt.h"
|
||||
#include "BEType.h"
|
||||
#include "StrUtil.h"
|
||||
#include "cfmt.h"
|
||||
#include "stdafx.h"
|
||||
#pragma warning(push)
|
||||
#pragma message("TODO: remove wx dependency: <wx/string.h>")
|
||||
#pragma warning(disable : 4996)
|
||||
#include <wx/string.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
template <>
|
||||
void fmt_class_string<std::pair<const fmt_type_info*, u64>>::format(std::string& out, u64 arg)
|
||||
std::string v128::to_hex() const
|
||||
{
|
||||
// Dynamic format arg
|
||||
const auto& pair = get_object(arg);
|
||||
return fmt::format("%016llx%016llx", _u64[1], _u64[0]);
|
||||
}
|
||||
|
||||
if (pair.first)
|
||||
std::string v128::to_xyzw() const
|
||||
{
|
||||
return fmt::format("x: %g y: %g z: %g w: %g", _f[3], _f[2], _f[1], _f[0]);
|
||||
}
|
||||
|
||||
std::string fmt::to_hex(u64 value, u64 count)
|
||||
{
|
||||
if (count - 1 >= 16)
|
||||
{
|
||||
pair.first->fmt_string(out, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void fmt_class_string<const void*>::format(std::string& out, u64 arg)
|
||||
{
|
||||
if (arg)
|
||||
{
|
||||
fmt::append(out, "%p", reinterpret_cast<const void*>(static_cast<std::uintptr_t>(arg)));
|
||||
}
|
||||
else
|
||||
{
|
||||
out += "(NULL)";
|
||||
}
|
||||
}
|
||||
|
||||
void fmt_class_string<const char*>::format(std::string& out, u64 arg)
|
||||
{
|
||||
if (arg)
|
||||
{
|
||||
out += reinterpret_cast<const char*>(static_cast<std::uintptr_t>(arg));
|
||||
}
|
||||
else
|
||||
{
|
||||
out += "(NULL)";
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<std::string>::format(std::string& out, u64 arg)
|
||||
{
|
||||
out += get_object(arg).c_str(); // TODO?
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<std::vector<char>>::format(std::string& out, u64 arg)
|
||||
{
|
||||
const std::vector<char>& obj = get_object(arg);
|
||||
out.append(obj.cbegin(), obj.cend());
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<char>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#hhx", static_cast<char>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<uchar>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#hhx", static_cast<uchar>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<schar>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#hhx", static_cast<schar>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<short>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#hx", static_cast<short>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<ushort>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#hx", static_cast<ushort>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<int>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#x", static_cast<int>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<uint>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#x", static_cast<uint>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<long>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#lx", static_cast<long>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<ulong>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#lx", static_cast<ulong>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<llong>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#llx", static_cast<llong>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<ullong>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%#llx", static_cast<ullong>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<float>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%gf", static_cast<float>(reinterpret_cast<f64&>(arg)));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<double>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%g", reinterpret_cast<f64&>(arg));
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<bool>::format(std::string& out, u64 arg)
|
||||
{
|
||||
out += arg ? "true" : "false";
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<v128>::format(std::string& out, u64 arg)
|
||||
{
|
||||
const v128& vec = get_object(arg);
|
||||
fmt::append(out, "0x%016llx%016llx", vec._u64[1], vec._u64[0]);
|
||||
}
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
void raw_error(const char* msg)
|
||||
{
|
||||
throw std::runtime_error{msg};
|
||||
throw EXCEPTION("Invalid count: 0x%llx", count);
|
||||
}
|
||||
|
||||
void raw_verify_error(const char* msg, const fmt_type_info* sup, u64 arg)
|
||||
count = std::max<u64>(count, 16 - cntlz64(value) / 4);
|
||||
|
||||
char res[16] = {};
|
||||
|
||||
for (size_t i = count - 1; ~i; i--, value /= 16)
|
||||
{
|
||||
std::string out{"Verification failed"};
|
||||
|
||||
// Print error code (may be irrelevant)
|
||||
#ifdef _WIN32
|
||||
if (DWORD error = GetLastError())
|
||||
{
|
||||
fmt::append(out, " (e=%#x)", error);
|
||||
}
|
||||
#else
|
||||
if (int error = errno)
|
||||
{
|
||||
fmt::append(out, " (e=%d)", error);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sup)
|
||||
{
|
||||
out += " (";
|
||||
sup->fmt_string(out, arg); // Print value
|
||||
out += ")";
|
||||
}
|
||||
|
||||
if (msg)
|
||||
{
|
||||
out += ": ";
|
||||
out += msg;
|
||||
}
|
||||
|
||||
throw std::runtime_error{out};
|
||||
res[i] = "0123456789abcdef"[value % 16];
|
||||
}
|
||||
|
||||
void raw_narrow_error(const char* msg, const fmt_type_info* sup, u64 arg)
|
||||
{
|
||||
std::string out{"Narrow error"};
|
||||
|
||||
if (sup)
|
||||
{
|
||||
out += " (";
|
||||
sup->fmt_string(out, arg); // Print value
|
||||
out += ")";
|
||||
}
|
||||
|
||||
if (msg)
|
||||
{
|
||||
out += ": ";
|
||||
out += msg;
|
||||
}
|
||||
|
||||
throw std::range_error{out};
|
||||
}
|
||||
|
||||
// Hidden template
|
||||
template <typename T>
|
||||
void raw_throw_exception(const char* fmt, const fmt_type_info* sup, const u64* args)
|
||||
{
|
||||
std::string out;
|
||||
raw_append(out, fmt, sup, args);
|
||||
throw T{out};
|
||||
}
|
||||
|
||||
// Explicit instantiations (not exhaustive)
|
||||
template void raw_throw_exception<std::runtime_error>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::logic_error>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::domain_error>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::invalid_argument>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::out_of_range>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::range_error>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::overflow_error>(const char*, const fmt_type_info*, const u64*);
|
||||
template void raw_throw_exception<std::underflow_error>(const char*, const fmt_type_info*, const u64*);
|
||||
|
||||
struct cfmt_src;
|
||||
return std::string(res, count);
|
||||
}
|
||||
|
||||
// Temporary implementation
|
||||
struct fmt::cfmt_src
|
||||
std::string fmt::to_udec(u64 value)
|
||||
{
|
||||
const fmt_type_info* sup;
|
||||
const u64* args;
|
||||
char res[20] = {};
|
||||
size_t first = sizeof(res);
|
||||
|
||||
bool test(std::size_t index) const
|
||||
if (!value)
|
||||
{
|
||||
if (!sup[index].fmt_string)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
res[--first] = '0';
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get(std::size_t index) const
|
||||
for (; value; value /= 10)
|
||||
{
|
||||
return *reinterpret_cast<const T*>(reinterpret_cast<const u8*>(args + index));
|
||||
res[--first] = '0' + (value % 10);
|
||||
}
|
||||
|
||||
void skip(std::size_t extra)
|
||||
{
|
||||
sup += extra + 1;
|
||||
args += extra + 1;
|
||||
}
|
||||
|
||||
std::size_t fmt_string(std::string& out, std::size_t extra) const
|
||||
{
|
||||
const std::size_t start = out.size();
|
||||
sup[extra].fmt_string(out, args[extra]);
|
||||
return out.size() - start;
|
||||
}
|
||||
|
||||
// Returns type size (0 if unknown, pointer, unsigned, assumed max)
|
||||
std::size_t type(std::size_t extra) const
|
||||
{
|
||||
// Hack: use known function pointers to determine type
|
||||
#define TYPE(type) \
|
||||
if (sup[extra].fmt_string == &fmt_class_string<type>::format) return sizeof(type);
|
||||
|
||||
TYPE(int);
|
||||
TYPE(llong);
|
||||
TYPE(schar);
|
||||
TYPE(short);
|
||||
if (std::is_signed<char>::value) TYPE(char);
|
||||
TYPE(long);
|
||||
|
||||
#undef TYPE
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr std::size_t size_char = 1;
|
||||
static constexpr std::size_t size_short = 2;
|
||||
static constexpr std::size_t size_int = 0;
|
||||
static constexpr std::size_t size_long = sizeof(ulong);
|
||||
static constexpr std::size_t size_llong = sizeof(ullong);
|
||||
static constexpr std::size_t size_size = sizeof(std::size_t);
|
||||
static constexpr std::size_t size_max = sizeof(std::uintmax_t);
|
||||
static constexpr std::size_t size_diff = sizeof(std::ptrdiff_t);
|
||||
};
|
||||
|
||||
void fmt::raw_append(std::string& out, const char* fmt, const fmt_type_info* sup, const u64* args) noexcept
|
||||
{
|
||||
cfmt_append(out, fmt, cfmt_src{sup, args});
|
||||
return std::string(&res[first], sizeof(res) - first);
|
||||
}
|
||||
|
||||
std::string fmt::to_sdec(s64 svalue)
|
||||
{
|
||||
const bool sign = svalue < 0;
|
||||
u64 value = sign ? -svalue : svalue;
|
||||
|
||||
char res[20] = {};
|
||||
size_t first = sizeof(res);
|
||||
|
||||
if (!value)
|
||||
{
|
||||
res[--first] = '0';
|
||||
}
|
||||
|
||||
for (; value; value /= 10)
|
||||
{
|
||||
res[--first] = '0' + (value % 10);
|
||||
}
|
||||
|
||||
if (sign)
|
||||
{
|
||||
res[--first] = '-';
|
||||
}
|
||||
|
||||
return std::string(&res[first], sizeof(res) - first);
|
||||
}
|
||||
|
||||
//extern const std::string fmt::placeholder = "???";
|
||||
|
||||
std::string fmt::replace_first(const std::string& src, const std::string& from, const std::string& to)
|
||||
{
|
||||
auto pos = src.find(from);
|
||||
@@ -314,7 +92,7 @@ std::string fmt::replace_first(const std::string& src, const std::string& from,
|
||||
return (pos ? src.substr(0, pos) + to : to) + std::string(src.c_str() + pos + from.length());
|
||||
}
|
||||
|
||||
std::string fmt::replace_all(const std::string& src, const std::string& from, const std::string& to)
|
||||
std::string fmt::replace_all(const std::string &src, const std::string& from, const std::string& to)
|
||||
{
|
||||
std::string target = src;
|
||||
for (auto pos = target.find(from); pos != std::string::npos; pos = target.find(from, pos + 1))
|
||||
@@ -326,6 +104,83 @@ std::string fmt::replace_all(const std::string& src, const std::string& from, co
|
||||
return target;
|
||||
}
|
||||
|
||||
//TODO: move this wx Stuff somewhere else
|
||||
//convert a wxString to a std::string encoded in utf8
|
||||
//CAUTION, only use this to interface with wxWidgets classes
|
||||
std::string fmt::ToUTF8(const wxString& right)
|
||||
{
|
||||
auto ret = std::string(((const char *)right.utf8_str()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
//convert a std::string encoded in utf8 to a wxString
|
||||
//CAUTION, only use this to interface with wxWidgets classes
|
||||
wxString fmt::FromUTF8(const std::string& right)
|
||||
{
|
||||
auto ret = wxString::FromUTF8(right.c_str());
|
||||
return ret;
|
||||
}
|
||||
|
||||
//TODO: remove this after every snippet that uses it is gone
|
||||
//WARNING: not fully compatible with CmpNoCase from wxString
|
||||
int fmt::CmpNoCase(const std::string& a, const std::string& b)
|
||||
{
|
||||
if (a.length() != b.length())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::equal(a.begin(),
|
||||
a.end(),
|
||||
b.begin(),
|
||||
[](const char& a, const char& b){return ::tolower(a) == ::tolower(b); })
|
||||
? 0 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: remove this after every snippet that uses it is gone
|
||||
//WARNING: not fully compatible with CmpNoCase from wxString
|
||||
void fmt::Replace(std::string &str, const std::string &searchterm, const std::string& replaceterm)
|
||||
{
|
||||
size_t cursor = 0;
|
||||
do
|
||||
{
|
||||
cursor = str.find(searchterm, cursor);
|
||||
if (cursor != std::string::npos)
|
||||
{
|
||||
str.replace(cursor, searchterm.size(), replaceterm);
|
||||
cursor += replaceterm.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
std::vector<std::string> fmt::rSplit(const std::string& source, const std::string& delim)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
size_t cursor = 0;
|
||||
do
|
||||
{
|
||||
size_t prevcurs = cursor;
|
||||
cursor = source.find(delim, cursor);
|
||||
if (cursor != std::string::npos)
|
||||
{
|
||||
ret.push_back(source.substr(prevcurs,cursor-prevcurs));
|
||||
cursor += delim.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.push_back(source.substr(prevcurs));
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> fmt::split(const std::string& source, std::initializer_list<std::string> separators, bool is_skip_empty)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
@@ -334,7 +189,7 @@ std::vector<std::string> fmt::split(const std::string& source, std::initializer_
|
||||
|
||||
for (size_t cursor_end = 0; cursor_end < source.length(); ++cursor_end)
|
||||
{
|
||||
for (auto& separator : separators)
|
||||
for (auto &separator : separators)
|
||||
{
|
||||
if (strncmp(source.c_str() + cursor_end, separator.c_str(), separator.length()) == 0)
|
||||
{
|
||||
@@ -343,7 +198,7 @@ std::vector<std::string> fmt::split(const std::string& source, std::initializer_
|
||||
result.push_back(candidate);
|
||||
|
||||
cursor_begin = cursor_end + separator.length();
|
||||
cursor_end = cursor_begin - 1;
|
||||
cursor_end = cursor_begin - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -362,20 +217,50 @@ std::string fmt::trim(const std::string& source, const std::string& values)
|
||||
std::size_t begin = source.find_first_not_of(values);
|
||||
|
||||
if (begin == source.npos)
|
||||
return {};
|
||||
return{};
|
||||
|
||||
return source.substr(begin, source.find_last_not_of(values) + 1);
|
||||
}
|
||||
|
||||
std::string fmt::to_upper(const std::string& string)
|
||||
std::string fmt::tolower(std::string source)
|
||||
{
|
||||
std::string result;
|
||||
result.resize(string.size());
|
||||
std::transform(string.begin(), string.end(), result.begin(), ::toupper);
|
||||
return result;
|
||||
std::transform(source.begin(), source.end(), source.begin(), ::tolower);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
bool fmt::match(const std::string& source, const std::string& mask)
|
||||
std::string fmt::toupper(std::string source)
|
||||
{
|
||||
std::transform(source.begin(), source.end(), source.begin(), ::toupper);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string fmt::escape(std::string source)
|
||||
{
|
||||
const std::pair<std::string, std::string> escape_list[] =
|
||||
{
|
||||
{ "\\", "\\\\" },
|
||||
{ "\a", "\\a" },
|
||||
{ "\b", "\\b" },
|
||||
{ "\f", "\\f" },
|
||||
{ "\n", "\\n\n" },
|
||||
{ "\r", "\\r" },
|
||||
{ "\t", "\\t" },
|
||||
{ "\v", "\\v" },
|
||||
};
|
||||
|
||||
source = fmt::replace_all(source, escape_list);
|
||||
|
||||
for (char c = 0; c < 32; c++)
|
||||
{
|
||||
if (c != '\n') source = fmt::replace_all(source, std::string(1, c), fmt::format("\\x%02X", c));
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
bool fmt::match(const std::string &source, const std::string &mask)
|
||||
{
|
||||
std::size_t source_position = 0, mask_position = 0;
|
||||
|
||||
|
||||
+315
-247
@@ -1,291 +1,359 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
class wxString;
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
template <typename... Args>
|
||||
static std::string format(const char*, const Args&...);
|
||||
}
|
||||
//struct empty_t{};
|
||||
|
||||
template <typename T, typename>
|
||||
struct fmt_unveil
|
||||
{
|
||||
static_assert(sizeof(T) > 0, "fmt_unveil<> error: incomplete type");
|
||||
//extern const std::string placeholder;
|
||||
|
||||
using type = T;
|
||||
|
||||
static inline u64 get(const T& arg)
|
||||
template <typename T>
|
||||
std::string AfterLast(const std::string& source, T searchstr)
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(&arg);
|
||||
size_t search_pos = source.rfind(searchstr);
|
||||
search_pos = search_pos == std::string::npos ? 0 : search_pos;
|
||||
return source.substr(search_pos);
|
||||
}
|
||||
|
||||
// Temporary value container (can possibly be created by other fmt_unveil<> specializations)
|
||||
struct u64_wrapper
|
||||
template <typename T>
|
||||
std::string BeforeLast(const std::string& source, T searchstr)
|
||||
{
|
||||
T arg;
|
||||
size_t search_pos = source.rfind(searchstr);
|
||||
search_pos = search_pos == std::string::npos ? 0 : search_pos;
|
||||
return source.substr(0, search_pos);
|
||||
}
|
||||
|
||||
// Allow implicit conversion
|
||||
operator u64() const
|
||||
template <typename T>
|
||||
std::string AfterFirst(const std::string& source, T searchstr)
|
||||
{
|
||||
size_t search_pos = source.find(searchstr);
|
||||
search_pos = search_pos == std::string::npos ? 0 : search_pos;
|
||||
return source.substr(search_pos);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string BeforeFirst(const std::string& source, T searchstr)
|
||||
{
|
||||
size_t search_pos = source.find(searchstr);
|
||||
search_pos = search_pos == std::string::npos ? 0 : search_pos;
|
||||
return source.substr(0, search_pos);
|
||||
}
|
||||
|
||||
// write `fmt` from `pos` to the first occurence of `fmt::placeholder` to
|
||||
// the stream `os`. Then write `arg` to to the stream. If there's no
|
||||
// `fmt::placeholder` after `pos` everything in `fmt` after pos is written
|
||||
// to `os`. Then `arg` is written to `os` after appending a space character
|
||||
//template<typename T>
|
||||
//empty_t write(const std::string &fmt, std::ostream &os, std::string::size_type &pos, T &&arg)
|
||||
//{
|
||||
// std::string::size_type ins = fmt.find(placeholder, pos);
|
||||
|
||||
// if (ins == std::string::npos)
|
||||
// {
|
||||
// os.write(fmt.data() + pos, fmt.size() - pos);
|
||||
// os << ' ' << arg;
|
||||
|
||||
// pos = fmt.size();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// os.write(fmt.data() + pos, ins - pos);
|
||||
// os << arg;
|
||||
|
||||
// pos = ins + placeholder.size();
|
||||
// }
|
||||
// return{};
|
||||
//}
|
||||
|
||||
// typesafe version of a sprintf-like function. Returns the printed to
|
||||
// string. To mark positions where the arguments are supposed to be
|
||||
// inserted use `fmt::placeholder`. If there's not enough placeholders
|
||||
// the rest of the arguments are appended at the end, seperated by spaces
|
||||
//template<typename ... Args>
|
||||
//std::string SFormat(const std::string &fmt, Args&& ... parameters)
|
||||
//{
|
||||
// std::ostringstream os;
|
||||
// std::string::size_type pos = 0;
|
||||
// std::initializer_list<empty_t> { write(fmt, os, pos, parameters)... };
|
||||
|
||||
// if (!fmt.empty())
|
||||
// {
|
||||
// os.write(fmt.data() + pos, fmt.size() - pos);
|
||||
// }
|
||||
|
||||
// std::string result = os.str();
|
||||
// return result;
|
||||
//}
|
||||
|
||||
std::string replace_first(const std::string& src, const std::string& from, const std::string& to);
|
||||
std::string replace_all(const std::string &src, const std::string& from, const std::string& to);
|
||||
|
||||
template<size_t list_size>
|
||||
std::string replace_all(std::string src, const std::pair<std::string, std::string>(&list)[list_size])
|
||||
{
|
||||
for (size_t pos = 0; pos < src.length(); ++pos)
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(&arg);
|
||||
}
|
||||
};
|
||||
|
||||
// This overload resolution takes the precedence
|
||||
static inline u64_wrapper get(T&& arg)
|
||||
{
|
||||
return u64_wrapper{std::move(arg)};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct fmt_unveil<T, std::enable_if_t<std::is_integral<T>::value && sizeof(T) <= 8 && alignof(T) <= 8>>
|
||||
{
|
||||
using type = T;
|
||||
|
||||
static inline u64 get(T arg)
|
||||
{
|
||||
return static_cast<T>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct fmt_unveil<T, std::enable_if_t<std::is_floating_point<T>::value && sizeof(T) <= 8 && alignof(T) <= 8>>
|
||||
{
|
||||
using type = T;
|
||||
|
||||
// Convert FP to f64 and reinterpret as u64
|
||||
static inline u64 get(const f64& arg)
|
||||
{
|
||||
return *reinterpret_cast<const u64*>(reinterpret_cast<const u8*>(&arg));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt_unveil<f16, void>
|
||||
{
|
||||
using type = f16;
|
||||
|
||||
static inline u64 get(const f16& arg)
|
||||
{
|
||||
return fmt_unveil<f64>::get(arg.operator float());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct fmt_unveil<T, std::enable_if_t<std::is_enum<T>::value>>
|
||||
{
|
||||
using type = T;
|
||||
|
||||
static inline u64 get(T arg)
|
||||
{
|
||||
return static_cast<std::underlying_type_t<T>>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct fmt_unveil<T*, void>
|
||||
{
|
||||
using type = const T*;
|
||||
|
||||
static inline u64 get(const T* arg)
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, std::size_t N>
|
||||
struct fmt_unveil<T[N], void>
|
||||
{
|
||||
using type = const T*;
|
||||
|
||||
static inline u64 get(const T* arg)
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt_unveil<b8, void>
|
||||
{
|
||||
using type = bool;
|
||||
|
||||
static inline u64 get(const b8& value)
|
||||
{
|
||||
return fmt_unveil<bool>::get(value);
|
||||
}
|
||||
};
|
||||
|
||||
// String type format provider, also type classifier (format() called if an argument is formatted as "%s")
|
||||
template <typename T, typename = void>
|
||||
struct fmt_class_string
|
||||
{
|
||||
// Formatting function (must be explicitly specialized)
|
||||
static void format(std::string& out, u64 arg);
|
||||
|
||||
// Helper typedef (visible in format())
|
||||
using type = T;
|
||||
|
||||
// Helper function (converts arg to object reference)
|
||||
static SAFE_BUFFERS FORCE_INLINE const T& get_object(u64 arg)
|
||||
{
|
||||
return *reinterpret_cast<const T*>(static_cast<std::uintptr_t>(arg));
|
||||
}
|
||||
|
||||
// Enum -> string function type
|
||||
using convert_t = const char*(*)(T value);
|
||||
|
||||
// Enum -> string function registered
|
||||
static convert_t convert_enum;
|
||||
|
||||
// Helper function (safely converts arg to enum value)
|
||||
static SAFE_BUFFERS FORCE_INLINE void format_enum(std::string& out, u64 arg, convert_t convert)
|
||||
{
|
||||
// Save convert function
|
||||
if (convert_enum == nullptr)
|
||||
{
|
||||
convert_enum = convert;
|
||||
}
|
||||
|
||||
const auto value = static_cast<std::underlying_type_t<T>>(arg);
|
||||
|
||||
// Check narrowing
|
||||
if (static_cast<u64>(value) == arg)
|
||||
{
|
||||
if (const char* str = convert(static_cast<T>(value)))
|
||||
for (size_t i = 0; i < list_size; ++i)
|
||||
{
|
||||
out += str;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const size_t comp_length = list[i].first.length();
|
||||
|
||||
// Fallback to underlying type formatting
|
||||
fmt_class_string<std::underlying_type_t<T>>::format(out, static_cast<u64>(value));
|
||||
}
|
||||
if (src.length() - pos < comp_length)
|
||||
continue;
|
||||
|
||||
// Helper function (bitset formatting)
|
||||
static SAFE_BUFFERS FORCE_INLINE void format_bitset(std::string& out, u64 arg, const char* prefix, const char* delim, const char* suffix, void (*fmt)(std::string&, u64))
|
||||
{
|
||||
// Start from raw value
|
||||
fmt_class_string<u64>::format(out, arg);
|
||||
|
||||
out += prefix;
|
||||
|
||||
for (u64 i = 0; i < 63; i++)
|
||||
{
|
||||
const u64 mask = 1ull << i;
|
||||
|
||||
if (arg & mask)
|
||||
{
|
||||
fmt(out, i);
|
||||
|
||||
if (arg >> (i + 1))
|
||||
if (src.substr(pos, comp_length) == list[i].first)
|
||||
{
|
||||
out += delim;
|
||||
src = (pos ? src.substr(0, pos) + list[i].second : list[i].second) + src.substr(pos + comp_length);
|
||||
pos += list[i].second.length() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arg & (1ull << 63))
|
||||
return src;
|
||||
}
|
||||
|
||||
template<size_t list_size>
|
||||
std::string replace_all(std::string src, const std::pair<std::string, std::function<std::string()>>(&list)[list_size])
|
||||
{
|
||||
for (size_t pos = 0; pos < src.length(); ++pos)
|
||||
{
|
||||
fmt(out, 63);
|
||||
for (size_t i = 0; i < list_size; ++i)
|
||||
{
|
||||
const size_t comp_length = list[i].first.length();
|
||||
|
||||
if (src.length() - pos < comp_length)
|
||||
continue;
|
||||
|
||||
if (src.substr(pos, comp_length) == list[i].first)
|
||||
{
|
||||
src = (pos ? src.substr(0, pos) + list[i].second() : list[i].second()) + src.substr(pos + comp_length);
|
||||
pos += list[i].second().length() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out += suffix;
|
||||
return src;
|
||||
}
|
||||
|
||||
// Helper constant (may be used in format_enum as lambda return value)
|
||||
static constexpr const char* unknown = nullptr;
|
||||
};
|
||||
std::string to_hex(u64 value, u64 count = 1);
|
||||
std::string to_udec(u64 value);
|
||||
std::string to_sdec(s64 value);
|
||||
|
||||
template <typename T, typename V>
|
||||
const char*(*fmt_class_string<T, V>::convert_enum)(T) = nullptr;
|
||||
|
||||
template <>
|
||||
struct fmt_class_string<const void*, void>
|
||||
{
|
||||
static void format(std::string& out, u64 arg);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct fmt_class_string<T*, void> : fmt_class_string<const void*, void>
|
||||
{
|
||||
// Classify all pointers as const void*
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt_class_string<const char*, void>
|
||||
{
|
||||
static void format(std::string& out, u64 arg);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt_class_string<char*, void> : fmt_class_string<const char*>
|
||||
{
|
||||
// Classify char* as const char*
|
||||
};
|
||||
|
||||
struct fmt_type_info
|
||||
{
|
||||
decltype(&fmt_class_string<int>::format) fmt_string;
|
||||
|
||||
template <typename T>
|
||||
static constexpr fmt_type_info make()
|
||||
template<typename T, bool is_enum = std::is_enum<T>::value> struct unveil
|
||||
{
|
||||
return fmt_type_info
|
||||
using result_type = T;
|
||||
|
||||
force_inline static result_type get_value(const T& arg)
|
||||
{
|
||||
&fmt_class_string<T>::format,
|
||||
};
|
||||
}
|
||||
};
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
// Argument array type (each element generated via fmt_unveil<>)
|
||||
template <typename... Args>
|
||||
using fmt_args_t = const u64(&&)[sizeof...(Args) + 1];
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
template <typename... Args>
|
||||
SAFE_BUFFERS FORCE_INLINE const fmt_type_info* get_type_info()
|
||||
template<> struct unveil<const char*, false>
|
||||
{
|
||||
// Constantly initialized null-terminated list of type-specific information
|
||||
static constexpr fmt_type_info result[sizeof...(Args) + 1]{fmt_type_info::make<Args>()...};
|
||||
using result_type = const char* const;
|
||||
|
||||
return result;
|
||||
force_inline static result_type get_value(const char* const& arg)
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
template<std::size_t N> struct unveil<char[N], false>
|
||||
{
|
||||
using result_type = const char* const;
|
||||
|
||||
force_inline static result_type get_value(const char(&arg)[N])
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct unveil<std::string, false>
|
||||
{
|
||||
using result_type = const char*;
|
||||
|
||||
force_inline static result_type get_value(const std::string& arg)
|
||||
{
|
||||
return arg.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct unveil<T, true>
|
||||
{
|
||||
using result_type = std::underlying_type_t<T>;
|
||||
|
||||
force_inline static result_type get_value(const T& arg)
|
||||
{
|
||||
return static_cast<result_type>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, bool Se> struct unveil<se_t<T, Se>, false>
|
||||
{
|
||||
using result_type = typename unveil<T>::result_type;
|
||||
|
||||
force_inline static result_type get_value(const se_t<T, Se>& arg)
|
||||
{
|
||||
return unveil<T>::get_value(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
force_inline typename unveil<T>::result_type do_unveil(const T& arg)
|
||||
{
|
||||
return unveil<T>::get_value(arg);
|
||||
}
|
||||
|
||||
// Internal formatting function
|
||||
void raw_append(std::string& out, const char*, const fmt_type_info*, const u64*) noexcept;
|
||||
|
||||
// Formatting function
|
||||
template <typename... Args>
|
||||
SAFE_BUFFERS FORCE_INLINE void append(std::string& out, const char* fmt, const Args&... args)
|
||||
// Formatting function with special functionality:
|
||||
//
|
||||
// std::string is forced to .c_str()
|
||||
// be_t<> is forced to .value() (fmt::do_unveil reverts byte order automatically)
|
||||
//
|
||||
// External specializations for fmt::do_unveil (can be found in another headers):
|
||||
// vm::ptr, vm::bptr, ... (fmt::do_unveil) (vm_ptr.h) (with appropriate address type, using .addr() can be avoided)
|
||||
// vm::ref, vm::bref, ... (fmt::do_unveil) (vm_ref.h)
|
||||
//
|
||||
template<typename... Args>
|
||||
safe_buffers std::string format(const char* fmt, const Args&... args)
|
||||
{
|
||||
raw_append(out, fmt, fmt::get_type_info<fmt_unveil_t<Args>...>(), fmt_args_t<Args...>{fmt_unveil<Args>::get(args)...});
|
||||
// fixed stack buffer for the first attempt
|
||||
std::array<char, 4096> fixed_buf;
|
||||
|
||||
// possibly dynamically allocated buffer for the second attempt
|
||||
std::unique_ptr<char[]> buf;
|
||||
|
||||
// pointer to the current buffer
|
||||
char* buf_addr = fixed_buf.data();
|
||||
|
||||
for (std::size_t buf_size = fixed_buf.size();;)
|
||||
{
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-security"
|
||||
#endif
|
||||
const std::size_t len = std::snprintf(buf_addr, buf_size, fmt, do_unveil(args)...);
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
if (len > INT_MAX)
|
||||
{
|
||||
throw std::runtime_error("std::snprintf() failed");
|
||||
}
|
||||
|
||||
if (len < buf_size)
|
||||
{
|
||||
return{ buf_addr, len };
|
||||
}
|
||||
|
||||
buf.reset(buf_addr = new char[buf_size = len + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Formatting function
|
||||
template <typename... Args>
|
||||
SAFE_BUFFERS FORCE_INLINE std::string format(const char* fmt, const Args&... args)
|
||||
struct exception : public std::exception
|
||||
{
|
||||
std::unique_ptr<char[]> message;
|
||||
|
||||
template<typename... Args> never_inline safe_buffers exception(const char* file, int line, const char* func, const char* text, Args... args) noexcept
|
||||
{
|
||||
const std::string data = format(text, args...) + format("\n(in file %s:%d, in function %s)", file, line, func);
|
||||
|
||||
message.reset(new char[data.size() + 1]);
|
||||
|
||||
std::memcpy(message.get(), data.c_str(), data.size() + 1);
|
||||
}
|
||||
|
||||
exception(const exception& other) noexcept
|
||||
{
|
||||
const std::size_t size = std::strlen(other.message.get());
|
||||
|
||||
message.reset(new char[size + 1]);
|
||||
|
||||
std::memcpy(message.get(), other.message.get(), size + 1);
|
||||
}
|
||||
|
||||
virtual const char* what() const noexcept override
|
||||
{
|
||||
return message.get();
|
||||
}
|
||||
};
|
||||
|
||||
//convert a wxString to a std::string encoded in utf8
|
||||
//CAUTION, only use this to interface with wxWidgets classes
|
||||
std::string ToUTF8(const wxString& right);
|
||||
|
||||
//convert a std::string encoded in utf8 to a wxString
|
||||
//CAUTION, only use this to interface with wxWidgets classes
|
||||
wxString FromUTF8(const std::string& right);
|
||||
|
||||
//TODO: remove this after every snippet that uses it is gone
|
||||
//WARNING: not fully compatible with CmpNoCase from wxString
|
||||
int CmpNoCase(const std::string& a, const std::string& b);
|
||||
|
||||
//TODO: remove this after every snippet that uses it is gone
|
||||
//WARNING: not fully compatible with Replace from wxString
|
||||
void Replace(std::string &str, const std::string &searchterm, const std::string& replaceterm);
|
||||
|
||||
std::vector<std::string> rSplit(const std::string& source, const std::string& delim);
|
||||
|
||||
std::vector<std::string> split(const std::string& source, std::initializer_list<std::string> separators, bool is_skip_empty = true);
|
||||
std::string trim(const std::string& source, const std::string& values = " \t");
|
||||
|
||||
template<typename T>
|
||||
std::string merge(const T& source, const std::string& separator)
|
||||
{
|
||||
if (!source.size())
|
||||
{
|
||||
return{};
|
||||
}
|
||||
|
||||
std::string result;
|
||||
append<Args...>(result, fmt, args...);
|
||||
|
||||
auto it = source.begin();
|
||||
auto end = source.end();
|
||||
for (--end; it != end; ++it)
|
||||
{
|
||||
result += *it + separator;
|
||||
}
|
||||
|
||||
return result + source.back();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string merge(std::initializer_list<T> sources, const std::string& separator)
|
||||
{
|
||||
if (!sources.size())
|
||||
{
|
||||
return{};
|
||||
}
|
||||
|
||||
std::string result;
|
||||
bool first = true;
|
||||
|
||||
for (auto &v : sources)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
result = fmt::merge(v, separator);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += separator + fmt::merge(v, separator);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Internal exception message formatting template, must be explicitly specialized or instantiated in cpp to minimize code bloat
|
||||
template <typename T>
|
||||
[[noreturn]] void raw_throw_exception(const char*, const fmt_type_info*, const u64*);
|
||||
|
||||
// Throw exception with formatting
|
||||
template <typename T = std::runtime_error, typename... Args>
|
||||
[[noreturn]] SAFE_BUFFERS FORCE_INLINE void throw_exception(const char* fmt, const Args&... args)
|
||||
{
|
||||
raw_throw_exception<T>(fmt, fmt::get_type_info<fmt_unveil_t<Args>...>(), fmt_args_t<Args...>{fmt_unveil<Args>::get(args)...});
|
||||
}
|
||||
std::string tolower(std::string source);
|
||||
std::string toupper(std::string source);
|
||||
std::string escape(std::string source);
|
||||
bool match(const std::string &source, const std::string &mask);
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
// Copy null-terminated string from std::string to char array with truncation
|
||||
template <std::size_t N>
|
||||
inline void strcpy_trunc(char (&dst)[N], const std::string& src)
|
||||
{
|
||||
const std::size_t count = src.size() >= N ? N - 1 : src.size();
|
||||
std::memcpy(dst, src.c_str(), count);
|
||||
dst[count] = '\0';
|
||||
}
|
||||
|
||||
// Copy null-terminated string from char array to another char array with truncation
|
||||
template <std::size_t N, std::size_t N2>
|
||||
inline void strcpy_trunc(char (&dst)[N], const char (&src)[N2])
|
||||
{
|
||||
const std::size_t count = N2 >= N ? N - 1 : N2;
|
||||
std::memcpy(dst, src, count);
|
||||
dst[count] = '\0';
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
inline bool ends_with(const std::string& src, const char (&end)[N])
|
||||
{
|
||||
return src.size() >= N - 1 && src.compare(src.size() - (N - 1), N - 1, end, N - 1) == 0;
|
||||
}
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
std::string replace_first(const std::string& src, const std::string& from, const std::string& to);
|
||||
std::string replace_all(const std::string& src, const std::string& from, const std::string& to);
|
||||
|
||||
template <size_t list_size>
|
||||
std::string replace_all(std::string src, const std::pair<std::string, std::string> (&list)[list_size])
|
||||
{
|
||||
for (size_t pos = 0; pos < src.length(); ++pos)
|
||||
{
|
||||
for (size_t i = 0; i < list_size; ++i)
|
||||
{
|
||||
const size_t comp_length = list[i].first.length();
|
||||
|
||||
if (src.length() - pos < comp_length)
|
||||
continue;
|
||||
|
||||
if (src.substr(pos, comp_length) == list[i].first)
|
||||
{
|
||||
src = (pos ? src.substr(0, pos) + list[i].second : list[i].second) + src.substr(pos + comp_length);
|
||||
pos += list[i].second.length() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
template <size_t list_size>
|
||||
std::string replace_all(std::string src, const std::pair<std::string, std::function<std::string()>> (&list)[list_size])
|
||||
{
|
||||
for (size_t pos = 0; pos < src.length(); ++pos)
|
||||
{
|
||||
for (size_t i = 0; i < list_size; ++i)
|
||||
{
|
||||
const size_t comp_length = list[i].first.length();
|
||||
|
||||
if (src.length() - pos < comp_length)
|
||||
continue;
|
||||
|
||||
if (src.substr(pos, comp_length) == list[i].first)
|
||||
{
|
||||
src = (pos ? src.substr(0, pos) + list[i].second() : list[i].second()) + src.substr(pos + comp_length);
|
||||
pos += list[i].second().length() - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string& source, std::initializer_list<std::string> separators, bool is_skip_empty = true);
|
||||
std::string trim(const std::string& source, const std::string& values = " \t");
|
||||
|
||||
template <typename T>
|
||||
std::string merge(const T& source, const std::string& separator)
|
||||
{
|
||||
if (!source.size())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
auto it = source.begin();
|
||||
auto end = source.end();
|
||||
for (--end; it != end; ++it)
|
||||
{
|
||||
result += *it + separator;
|
||||
}
|
||||
|
||||
return result + source.back();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string merge(std::initializer_list<T> sources, const std::string& separator)
|
||||
{
|
||||
if (!sources.size())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string result;
|
||||
bool first = true;
|
||||
|
||||
for (auto& v : sources)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
result = fmt::merge(v, separator);
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += separator + fmt::merge(v, separator);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_upper(const std::string& string);
|
||||
|
||||
bool match(const std::string& source, const std::string& mask);
|
||||
}
|
||||
+428
-1083
File diff suppressed because it is too large
Load Diff
+424
-269
@@ -1,355 +1,510 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "sema.h"
|
||||
#include "cond.h"
|
||||
|
||||
// Report error and call std::abort(), defined in main.cpp
|
||||
[[noreturn]] void report_fatal_error(const std::string&);
|
||||
|
||||
// Will report exception and call std::abort() if put in catch(...)
|
||||
[[noreturn]] void catch_all_exceptions();
|
||||
|
||||
// Hardware core layout
|
||||
enum class native_core_arrangement : u32
|
||||
{
|
||||
undefined,
|
||||
generic,
|
||||
intel_ht,
|
||||
amd_ccx
|
||||
};
|
||||
|
||||
enum class thread_class : u32
|
||||
{
|
||||
general,
|
||||
rsx,
|
||||
spu,
|
||||
ppu
|
||||
};
|
||||
|
||||
// Simple list of void() functors
|
||||
class task_stack
|
||||
{
|
||||
struct task_base
|
||||
{
|
||||
std::unique_ptr<task_base> next;
|
||||
|
||||
virtual ~task_base();
|
||||
|
||||
virtual void invoke()
|
||||
{
|
||||
if (next)
|
||||
{
|
||||
next->invoke();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct task_type final : task_base
|
||||
{
|
||||
std::remove_reference_t<F> func;
|
||||
|
||||
task_type(F&& func)
|
||||
: func(std::forward<F>(func))
|
||||
{
|
||||
}
|
||||
|
||||
void invoke() final override
|
||||
{
|
||||
func();
|
||||
task_base::invoke();
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<task_base> m_stack;
|
||||
|
||||
public:
|
||||
task_stack() = default;
|
||||
|
||||
template <typename F>
|
||||
task_stack(F&& func)
|
||||
: m_stack(new task_type<F>(std::forward<F>(func)))
|
||||
{
|
||||
}
|
||||
|
||||
void push(task_stack stack)
|
||||
{
|
||||
auto _top = stack.m_stack.release();
|
||||
auto _next = m_stack.release();
|
||||
m_stack.reset(_top);
|
||||
while (UNLIKELY(_top->next)) _top = _top->next.get();
|
||||
_top->next.reset(_next);
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_stack.reset();
|
||||
}
|
||||
|
||||
void invoke() const
|
||||
{
|
||||
if (m_stack)
|
||||
{
|
||||
m_stack->invoke();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Thread control class
|
||||
class thread_ctrl final
|
||||
{
|
||||
// Current thread
|
||||
static thread_local thread_ctrl* g_tls_this_thread;
|
||||
|
||||
// Target cpu core layout
|
||||
static atomic_t<native_core_arrangement> g_native_core_layout;
|
||||
// Name getter
|
||||
std::function<std::string()> m_name;
|
||||
|
||||
// Self pointer
|
||||
std::shared_ptr<thread_ctrl> m_self;
|
||||
// Thread handle (be careful)
|
||||
std::thread m_thread;
|
||||
|
||||
// Thread handle (platform-specific)
|
||||
atomic_t<std::uintptr_t> m_thread{0};
|
||||
// Thread result
|
||||
std::future<void> m_future;
|
||||
|
||||
// Thread mutex
|
||||
mutable semaphore<> m_mutex;
|
||||
|
||||
// Thread condition variable
|
||||
cond_variable m_cond;
|
||||
|
||||
// Thread flags
|
||||
atomic_t<u32> m_signal{0};
|
||||
|
||||
// Thread joining condition variable
|
||||
cond_variable m_jcv;
|
||||
|
||||
// Remotely set or caught exception
|
||||
std::exception_ptr m_exception;
|
||||
|
||||
// Thread initial task or atexit task
|
||||
task_stack m_task;
|
||||
|
||||
// Fixed name
|
||||
std::string m_name;
|
||||
|
||||
// Start thread
|
||||
static void start(const std::shared_ptr<thread_ctrl>&, task_stack);
|
||||
// Functions scheduled at thread exit
|
||||
std::deque<std::function<void()>> m_atexit;
|
||||
|
||||
// Called at the thread start
|
||||
void initialize();
|
||||
static void initialize();
|
||||
|
||||
// Called at the thread end
|
||||
void finalize(std::exception_ptr) noexcept;
|
||||
|
||||
// Add task (atexit)
|
||||
static void _push(task_stack);
|
||||
|
||||
// Internal waiting function, may throw. Infinite value is -1.
|
||||
static bool _wait_for(u64 usec);
|
||||
|
||||
// Internal throwing function. Mutex must be locked and will be unlocked.
|
||||
[[noreturn]] void _throw();
|
||||
|
||||
// Internal notification function
|
||||
void _notify(cond_variable thread_ctrl::*);
|
||||
static void finalize() noexcept;
|
||||
|
||||
public:
|
||||
thread_ctrl(std::string&& name);
|
||||
template<typename T>
|
||||
thread_ctrl(T&& name)
|
||||
: m_name(std::forward<T>(name))
|
||||
{
|
||||
}
|
||||
|
||||
// Disable copy/move constructors and operators
|
||||
thread_ctrl(const thread_ctrl&) = delete;
|
||||
|
||||
~thread_ctrl();
|
||||
|
||||
// Get thread name
|
||||
const std::string& get_name() const
|
||||
std::string get_name() const;
|
||||
|
||||
// Get future result (may throw)
|
||||
void join()
|
||||
{
|
||||
return m_name;
|
||||
return m_future.get();
|
||||
}
|
||||
|
||||
// Get exception
|
||||
std::exception_ptr get_exception() const;
|
||||
|
||||
// Set exception
|
||||
void set_exception(std::exception_ptr ptr);
|
||||
|
||||
// Get thread result (may throw, simultaneous joining allowed)
|
||||
void join();
|
||||
|
||||
// Notify the thread
|
||||
void notify();
|
||||
|
||||
// Wait once with timeout. Abortable, may throw. May spuriously return false.
|
||||
static inline bool wait_for(u64 usec)
|
||||
{
|
||||
return _wait_for(usec);
|
||||
}
|
||||
|
||||
// Wait. Abortable, may throw.
|
||||
static inline void wait()
|
||||
{
|
||||
_wait_for(-1);
|
||||
}
|
||||
|
||||
// Wait until pred(). Abortable, may throw.
|
||||
template<typename F, typename RT = std::result_of_t<F()>>
|
||||
static inline RT wait(F&& pred)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (RT result = pred())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
_wait_for(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait eternally until aborted.
|
||||
[[noreturn]] static inline void eternalize()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_wait_for(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Test exception (may throw).
|
||||
static void test();
|
||||
|
||||
// Get current thread (may be nullptr)
|
||||
static thread_ctrl* get_current()
|
||||
static const thread_ctrl* get_current()
|
||||
{
|
||||
return g_tls_this_thread;
|
||||
}
|
||||
|
||||
// Register function at thread exit (for the current thread)
|
||||
template<typename F>
|
||||
static inline void atexit(F&& func)
|
||||
template<typename T>
|
||||
static inline void at_exit(T&& func)
|
||||
{
|
||||
_push(std::forward<F>(func));
|
||||
}
|
||||
CHECK_ASSERTION(g_tls_this_thread);
|
||||
|
||||
// Create detached named thread
|
||||
template<typename N, typename F>
|
||||
static inline void spawn(N&& name, F&& func)
|
||||
{
|
||||
auto out = std::make_shared<thread_ctrl>(std::forward<N>(name));
|
||||
|
||||
thread_ctrl::start(out, std::forward<F>(func));
|
||||
g_tls_this_thread->m_atexit.emplace_front(std::forward<T>(func));
|
||||
}
|
||||
|
||||
// Named thread factory
|
||||
template<typename N, typename F>
|
||||
static inline void spawn(std::shared_ptr<thread_ctrl>& out, N&& name, F&& func)
|
||||
static inline std::shared_ptr<thread_ctrl> spawn(N&& name, F&& func)
|
||||
{
|
||||
out = std::make_shared<thread_ctrl>(std::forward<N>(name));
|
||||
auto ctrl = std::make_shared<thread_ctrl>(std::forward<N>(name));
|
||||
|
||||
thread_ctrl::start(out, std::forward<F>(func));
|
||||
std::promise<void> promise;
|
||||
|
||||
ctrl->m_future = promise.get_future();
|
||||
|
||||
ctrl->m_thread = std::thread([ctrl, task = std::forward<F>(func)](std::promise<void> promise)
|
||||
{
|
||||
g_tls_this_thread = ctrl.get();
|
||||
|
||||
try
|
||||
{
|
||||
initialize();
|
||||
task();
|
||||
finalize();
|
||||
promise.set_value();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
finalize();
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
|
||||
}, std::move(promise));
|
||||
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
// Detect layout
|
||||
static void detect_cpu_layout();
|
||||
|
||||
// Returns a core affinity mask. Set whether to generate the high priority set or not
|
||||
static u16 get_affinity_mask(thread_class group);
|
||||
|
||||
// Sets the native thread priority
|
||||
static void set_native_priority(int priority);
|
||||
|
||||
// Sets the preferred affinity mask for this thread
|
||||
static void set_thread_affinity_mask(u16 mask);
|
||||
};
|
||||
|
||||
class named_thread
|
||||
class named_thread_t : public std::enable_shared_from_this<named_thread_t>
|
||||
{
|
||||
// Pointer to managed resource (shared with actual thread)
|
||||
std::shared_ptr<thread_ctrl> m_thread;
|
||||
|
||||
public:
|
||||
named_thread();
|
||||
// Thread condition variable for external use (this thread waits on it, other threads may notify)
|
||||
std::condition_variable cv;
|
||||
|
||||
virtual ~named_thread();
|
||||
|
||||
// Deleted copy/move constructors + copy/move operators
|
||||
named_thread(const named_thread&) = delete;
|
||||
|
||||
// Get thread name
|
||||
virtual std::string get_name() const;
|
||||
// Thread mutex for external use (can be used with `cv`)
|
||||
std::mutex mutex;
|
||||
|
||||
protected:
|
||||
// Start thread (cannot be called from the constructor: should throw in such case)
|
||||
void start_thread(const std::shared_ptr<void>& _this);
|
||||
|
||||
// Thread task (called in the thread)
|
||||
virtual void on_task() = 0;
|
||||
|
||||
// Thread finalization (called after on_task)
|
||||
virtual void on_exit() {}
|
||||
|
||||
// Called once upon thread spawn within the thread's own context
|
||||
virtual void on_spawn() {}
|
||||
// ID initialization (called through id_aux_initialize)
|
||||
virtual void on_id_aux_initialize() { start(); }
|
||||
|
||||
// ID finalization (called through id_aux_finalize)
|
||||
virtual void on_id_aux_finalize() { join(); }
|
||||
|
||||
public:
|
||||
// ID initialization
|
||||
virtual void on_init(const std::shared_ptr<void>& _this)
|
||||
{
|
||||
return start_thread(_this);
|
||||
}
|
||||
named_thread_t() = default;
|
||||
|
||||
// ID finalization
|
||||
virtual void on_stop()
|
||||
{
|
||||
m_thread->join();
|
||||
}
|
||||
virtual ~named_thread_t() = default;
|
||||
|
||||
// Access thread_ctrl
|
||||
thread_ctrl* get() const
|
||||
{
|
||||
return m_thread.get();
|
||||
}
|
||||
// Deleted copy/move constructors + copy/move operators
|
||||
named_thread_t(const named_thread_t&) = delete;
|
||||
|
||||
void join() const
|
||||
{
|
||||
return m_thread->join();
|
||||
}
|
||||
// Get thread name
|
||||
virtual std::string get_name() const;
|
||||
|
||||
void notify() const
|
||||
{
|
||||
return m_thread->notify();
|
||||
}
|
||||
// Start thread (cannot be called from the constructor: should throw bad_weak_ptr in such case)
|
||||
void start();
|
||||
|
||||
// Join thread (get future result)
|
||||
void join();
|
||||
|
||||
// Check whether the thread is not in "empty state"
|
||||
bool is_started() const { return m_thread.operator bool(); }
|
||||
|
||||
// Compare with the current thread
|
||||
bool is_current() const { CHECK_ASSERTION(m_thread); return thread_ctrl::get_current() == m_thread.get(); }
|
||||
|
||||
// Get thread_ctrl
|
||||
const thread_ctrl* get_thread_ctrl() const { return m_thread.get(); }
|
||||
|
||||
friend void id_aux_initialize(named_thread_t* ptr) { ptr->on_id_aux_initialize(); }
|
||||
friend void id_aux_finalize(named_thread_t* ptr) { ptr->on_id_aux_finalize(); }
|
||||
};
|
||||
|
||||
// Wrapper for named thread, joins automatically in the destructor, can only be used in function scope
|
||||
class scope_thread final
|
||||
class scope_thread_t final
|
||||
{
|
||||
std::shared_ptr<thread_ctrl> m_thread;
|
||||
|
||||
public:
|
||||
template<typename N, typename F>
|
||||
scope_thread(N&& name, F&& func)
|
||||
scope_thread_t(N&& name, F&& func)
|
||||
: m_thread(thread_ctrl::spawn(std::forward<N>(name), std::forward<F>(func)))
|
||||
{
|
||||
thread_ctrl::spawn(m_thread, std::forward<N>(name), std::forward<F>(func));
|
||||
}
|
||||
|
||||
// Deleted copy/move constructors + copy/move operators
|
||||
scope_thread(const scope_thread&) = delete;
|
||||
scope_thread_t(const scope_thread_t&) = delete;
|
||||
|
||||
// Destructor with exceptions allowed
|
||||
~scope_thread() noexcept(false)
|
||||
~scope_thread_t() noexcept(false)
|
||||
{
|
||||
m_thread->join();
|
||||
}
|
||||
};
|
||||
|
||||
// Access thread_ctrl
|
||||
thread_ctrl* get() const
|
||||
extern const std::function<bool()> SQUEUE_ALWAYS_EXIT;
|
||||
extern const std::function<bool()> SQUEUE_NEVER_EXIT;
|
||||
|
||||
bool squeue_test_exit();
|
||||
|
||||
template<typename T, u32 sq_size = 256>
|
||||
class squeue_t
|
||||
{
|
||||
struct squeue_sync_var_t
|
||||
{
|
||||
return m_thread.get();
|
||||
struct
|
||||
{
|
||||
u32 position : 31;
|
||||
u32 pop_lock : 1;
|
||||
};
|
||||
struct
|
||||
{
|
||||
u32 count : 31;
|
||||
u32 push_lock : 1;
|
||||
};
|
||||
};
|
||||
|
||||
atomic_t<squeue_sync_var_t> m_sync;
|
||||
|
||||
mutable std::mutex m_rcv_mutex;
|
||||
mutable std::mutex m_wcv_mutex;
|
||||
mutable std::condition_variable m_rcv;
|
||||
mutable std::condition_variable m_wcv;
|
||||
|
||||
T m_data[sq_size];
|
||||
|
||||
enum squeue_sync_var_result : u32
|
||||
{
|
||||
SQSVR_OK = 0,
|
||||
SQSVR_LOCKED = 1,
|
||||
SQSVR_FAILED = 2,
|
||||
};
|
||||
|
||||
public:
|
||||
squeue_t()
|
||||
: m_sync(squeue_sync_var_t{})
|
||||
{
|
||||
}
|
||||
|
||||
u32 get_max_size() const
|
||||
{
|
||||
return sq_size;
|
||||
}
|
||||
|
||||
bool is_full() const
|
||||
{
|
||||
return m_sync.load().count == sq_size;
|
||||
}
|
||||
|
||||
bool push(const T& data, const std::function<bool()>& test_exit)
|
||||
{
|
||||
u32 pos = 0;
|
||||
|
||||
while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
|
||||
if (sync.push_lock)
|
||||
{
|
||||
return SQSVR_LOCKED;
|
||||
}
|
||||
if (sync.count == sq_size)
|
||||
{
|
||||
return SQSVR_FAILED;
|
||||
}
|
||||
|
||||
sync.push_lock = 1;
|
||||
pos = sync.position + sync.count;
|
||||
return SQSVR_OK;
|
||||
}))
|
||||
{
|
||||
if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> wcv_lock(m_wcv_mutex);
|
||||
m_wcv.wait_for(wcv_lock, std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
m_data[pos >= sq_size ? pos - sq_size : pos] = data;
|
||||
|
||||
m_sync.atomic_op([](squeue_sync_var_t& sync)
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
assert(sync.push_lock);
|
||||
sync.push_lock = 0;
|
||||
sync.count++;
|
||||
});
|
||||
|
||||
m_rcv.notify_one();
|
||||
m_wcv.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool push(const T& data, const volatile bool* do_exit)
|
||||
{
|
||||
return push(data, [do_exit](){ return do_exit && *do_exit; });
|
||||
}
|
||||
|
||||
force_inline bool push(const T& data)
|
||||
{
|
||||
return push(data, SQUEUE_NEVER_EXIT);
|
||||
}
|
||||
|
||||
force_inline bool try_push(const T& data)
|
||||
{
|
||||
return push(data, SQUEUE_ALWAYS_EXIT);
|
||||
}
|
||||
|
||||
bool pop(T& data, const std::function<bool()>& test_exit)
|
||||
{
|
||||
u32 pos = 0;
|
||||
|
||||
while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
|
||||
if (!sync.count)
|
||||
{
|
||||
return SQSVR_FAILED;
|
||||
}
|
||||
if (sync.pop_lock)
|
||||
{
|
||||
return SQSVR_LOCKED;
|
||||
}
|
||||
|
||||
sync.pop_lock = 1;
|
||||
pos = sync.position;
|
||||
return SQSVR_OK;
|
||||
}))
|
||||
{
|
||||
if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> rcv_lock(m_rcv_mutex);
|
||||
m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
data = m_data[pos];
|
||||
|
||||
m_sync.atomic_op([](squeue_sync_var_t& sync)
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
assert(sync.pop_lock);
|
||||
sync.pop_lock = 0;
|
||||
sync.position++;
|
||||
sync.count--;
|
||||
if (sync.position == sq_size)
|
||||
{
|
||||
sync.position = 0;
|
||||
}
|
||||
});
|
||||
|
||||
m_rcv.notify_one();
|
||||
m_wcv.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop(T& data, const volatile bool* do_exit)
|
||||
{
|
||||
return pop(data, [do_exit](){ return do_exit && *do_exit; });
|
||||
}
|
||||
|
||||
force_inline bool pop(T& data)
|
||||
{
|
||||
return pop(data, SQUEUE_NEVER_EXIT);
|
||||
}
|
||||
|
||||
force_inline bool try_pop(T& data)
|
||||
{
|
||||
return pop(data, SQUEUE_ALWAYS_EXIT);
|
||||
}
|
||||
|
||||
bool peek(T& data, u32 start_pos, const std::function<bool()>& test_exit)
|
||||
{
|
||||
assert(start_pos < sq_size);
|
||||
u32 pos = 0;
|
||||
|
||||
while (u32 res = m_sync.atomic_op([&pos, start_pos](squeue_sync_var_t& sync) -> u32
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
|
||||
if (sync.count <= start_pos)
|
||||
{
|
||||
return SQSVR_FAILED;
|
||||
}
|
||||
if (sync.pop_lock)
|
||||
{
|
||||
return SQSVR_LOCKED;
|
||||
}
|
||||
|
||||
sync.pop_lock = 1;
|
||||
pos = sync.position + start_pos;
|
||||
return SQSVR_OK;
|
||||
}))
|
||||
{
|
||||
if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> rcv_lock(m_rcv_mutex);
|
||||
m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
data = m_data[pos >= sq_size ? pos - sq_size : pos];
|
||||
|
||||
m_sync.atomic_op([](squeue_sync_var_t& sync)
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
assert(sync.pop_lock);
|
||||
sync.pop_lock = 0;
|
||||
});
|
||||
|
||||
m_rcv.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool peek(T& data, u32 start_pos, const volatile bool* do_exit)
|
||||
{
|
||||
return peek(data, start_pos, [do_exit](){ return do_exit && *do_exit; });
|
||||
}
|
||||
|
||||
force_inline bool peek(T& data, u32 start_pos = 0)
|
||||
{
|
||||
return peek(data, start_pos, SQUEUE_NEVER_EXIT);
|
||||
}
|
||||
|
||||
force_inline bool try_peek(T& data, u32 start_pos = 0)
|
||||
{
|
||||
return peek(data, start_pos, SQUEUE_ALWAYS_EXIT);
|
||||
}
|
||||
|
||||
class squeue_data_t
|
||||
{
|
||||
T* const m_data;
|
||||
const u32 m_pos;
|
||||
const u32 m_count;
|
||||
|
||||
squeue_data_t(T* data, u32 pos, u32 count)
|
||||
: m_data(data)
|
||||
, m_pos(pos)
|
||||
, m_count(count)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
T& operator [] (u32 index)
|
||||
{
|
||||
assert(index < m_count);
|
||||
index += m_pos;
|
||||
index = index < sq_size ? index : index - sq_size;
|
||||
return m_data[index];
|
||||
}
|
||||
};
|
||||
|
||||
void process(void(*proc)(squeue_data_t data))
|
||||
{
|
||||
u32 pos, count;
|
||||
|
||||
while (m_sync.atomic_op([&pos, &count](squeue_sync_var_t& sync) -> u32
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
|
||||
if (sync.pop_lock || sync.push_lock)
|
||||
{
|
||||
return SQSVR_LOCKED;
|
||||
}
|
||||
|
||||
pos = sync.position;
|
||||
count = sync.count;
|
||||
sync.pop_lock = 1;
|
||||
sync.push_lock = 1;
|
||||
return SQSVR_OK;
|
||||
}))
|
||||
{
|
||||
std::unique_lock<std::mutex> rcv_lock(m_rcv_mutex);
|
||||
m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
proc(squeue_data_t(m_data, pos, count));
|
||||
|
||||
m_sync.atomic_op([](squeue_sync_var_t& sync)
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
assert(sync.pop_lock && sync.push_lock);
|
||||
sync.pop_lock = 0;
|
||||
sync.push_lock = 0;
|
||||
});
|
||||
|
||||
m_wcv.notify_one();
|
||||
m_rcv.notify_one();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
while (m_sync.atomic_op([](squeue_sync_var_t& sync) -> u32
|
||||
{
|
||||
assert(sync.count <= sq_size);
|
||||
assert(sync.position < sq_size);
|
||||
|
||||
if (sync.pop_lock || sync.push_lock)
|
||||
{
|
||||
return SQSVR_LOCKED;
|
||||
}
|
||||
|
||||
sync.pop_lock = 1;
|
||||
sync.push_lock = 1;
|
||||
return SQSVR_OK;
|
||||
}))
|
||||
{
|
||||
std::unique_lock<std::mutex> rcv_lock(m_rcv_mutex);
|
||||
m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
m_sync.exchange({});
|
||||
m_wcv.notify_one();
|
||||
m_rcv.notify_one();
|
||||
}
|
||||
};
|
||||
|
||||
+6
-6
@@ -6,8 +6,8 @@ class Timer
|
||||
{
|
||||
private:
|
||||
bool m_stopped;
|
||||
std::chrono::steady_clock::time_point m_start;
|
||||
std::chrono::steady_clock::time_point m_end;
|
||||
std::chrono::high_resolution_clock::time_point m_start;
|
||||
std::chrono::high_resolution_clock::time_point m_end;
|
||||
|
||||
public:
|
||||
Timer() : m_stopped(false)
|
||||
@@ -17,13 +17,13 @@ public:
|
||||
void Start()
|
||||
{
|
||||
m_stopped = false;
|
||||
m_start = std::chrono::steady_clock::now();
|
||||
m_start = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Stop()
|
||||
{
|
||||
m_stopped = true;
|
||||
m_end = std::chrono::steady_clock::now();
|
||||
m_end = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
double GetElapsedTimeInSec() const
|
||||
@@ -38,14 +38,14 @@ public:
|
||||
|
||||
u64 GetElapsedTimeInMicroSec() const
|
||||
{
|
||||
std::chrono::steady_clock::time_point now = m_stopped ? m_end : std::chrono::steady_clock::now();
|
||||
std::chrono::high_resolution_clock::time_point now = m_stopped ? m_end : std::chrono::high_resolution_clock::now();
|
||||
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(now - m_start).count();
|
||||
}
|
||||
|
||||
u64 GetElapsedTimeInNanoSec() const
|
||||
{
|
||||
std::chrono::steady_clock::time_point now = m_stopped ? m_end : std::chrono::steady_clock::now();
|
||||
std::chrono::high_resolution_clock::time_point now = m_stopped ? m_end : std::chrono::high_resolution_clock::now();
|
||||
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(now - m_start).count();
|
||||
}
|
||||
|
||||
+13
-56
@@ -11,78 +11,35 @@
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
namespace utils
|
||||
namespace memory_helper
|
||||
{
|
||||
// Convert memory protection (internal)
|
||||
static auto operator +(protection prot)
|
||||
void* reserve_memory(size_t size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD _prot = PAGE_NOACCESS;
|
||||
switch (prot)
|
||||
{
|
||||
case protection::rw: _prot = PAGE_READWRITE; break;
|
||||
case protection::ro: _prot = PAGE_READONLY; break;
|
||||
case protection::no: break;
|
||||
case protection::wx: _prot = PAGE_EXECUTE_READWRITE; break;
|
||||
case protection::rx: _prot = PAGE_EXECUTE_READ; break;
|
||||
}
|
||||
void* ret = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS);
|
||||
CHECK_ASSERTION(ret != NULL);
|
||||
#else
|
||||
int _prot = PROT_NONE;
|
||||
switch (prot)
|
||||
{
|
||||
case protection::rw: _prot = PROT_READ | PROT_WRITE; break;
|
||||
case protection::ro: _prot = PROT_READ; break;
|
||||
case protection::no: break;
|
||||
case protection::wx: _prot = PROT_READ | PROT_WRITE | PROT_EXEC; break;
|
||||
case protection::rx: _prot = PROT_READ | PROT_EXEC; break;
|
||||
}
|
||||
void* ret = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
CHECK_ASSERTION(ret != 0);
|
||||
#endif
|
||||
|
||||
return _prot;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* memory_reserve(std::size_t size, void* use_addr)
|
||||
void commit_page_memory(void* pointer, size_t page_size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return ::VirtualAlloc(use_addr, size, MEM_RESERVE, PAGE_NOACCESS);
|
||||
CHECK_ASSERTION(VirtualAlloc((u8*)pointer, page_size, MEM_COMMIT, PAGE_READWRITE) != NULL);
|
||||
#else
|
||||
auto ptr = ::mmap(use_addr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
|
||||
if (use_addr && ptr != use_addr)
|
||||
{
|
||||
::munmap(ptr, size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
CHECK_ASSERTION(mprotect((u8*)pointer, page_size, PROT_READ | PROT_WRITE) != -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void memory_commit(void* pointer, std::size_t size, protection prot)
|
||||
void free_reserved_memory(void* pointer, size_t size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
verify(HERE), ::VirtualAlloc(pointer, size, MEM_COMMIT, +prot);
|
||||
CHECK_ASSERTION(VirtualFree(pointer, 0, MEM_RELEASE) != 0);
|
||||
#else
|
||||
verify(HERE), ::mprotect((void*)((u64)pointer & -4096), ::align(size, 4096), +prot) != -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void memory_decommit(void* pointer, std::size_t size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
verify(HERE), ::VirtualFree(pointer, size, MEM_DECOMMIT);
|
||||
#else
|
||||
verify(HERE), ::mmap(pointer, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void memory_protect(void* pointer, std::size_t size, protection prot)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD old;
|
||||
verify(HERE), ::VirtualProtect(pointer, size, +prot, &old);
|
||||
#else
|
||||
verify(HERE), ::mprotect((void*)((u64)pointer & -4096), ::align(size, 4096), +prot) != -1;
|
||||
CHECK_ASSERTION(munmap(pointer, size) == 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
namespace utils
|
||||
namespace memory_helper
|
||||
{
|
||||
// Memory protection type
|
||||
enum class protection
|
||||
{
|
||||
rw, // Read + write (default)
|
||||
ro, // Read only
|
||||
no, // No access
|
||||
wx, // Read + write + execute
|
||||
rx, // Read + execute
|
||||
};
|
||||
|
||||
/**
|
||||
* Reserve `size` bytes of virtual memory and returns it.
|
||||
* Reserve size bytes of virtual memory and returns it.
|
||||
* The memory should be commited before usage.
|
||||
*/
|
||||
void* memory_reserve(std::size_t size, void* use_addr = nullptr);
|
||||
void* reserve_memory(size_t size);
|
||||
|
||||
/**
|
||||
* Commit `size` bytes of virtual memory starting at pointer.
|
||||
* Commit page_size bytes of virtual memory starting at pointer.
|
||||
* That is, bake reserved memory with physical memory.
|
||||
* pointer should belong to a range of reserved memory.
|
||||
*/
|
||||
void memory_commit(void* pointer, std::size_t size, protection prot = protection::rw);
|
||||
void commit_page_memory(void* pointer, size_t page_size);
|
||||
|
||||
/**
|
||||
* Decommit all memory committed via commit_page_memory.
|
||||
* Free memory alloced via reserve_memory.
|
||||
*/
|
||||
void memory_decommit(void* pointer, std::size_t size);
|
||||
|
||||
// Set memory protection
|
||||
void memory_protect(void* pointer, std::size_t size, protection prot);
|
||||
}
|
||||
void free_reserved_memory(void* pointer, size_t size);
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
#include "bin_patch.h"
|
||||
#include "yaml-cpp/yaml.h"
|
||||
#include "File.h"
|
||||
#include "Config.h"
|
||||
|
||||
template <>
|
||||
void fmt_class_string<patch_type>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](patch_type value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case patch_type::load: return "load";
|
||||
case patch_type::byte: return "byte";
|
||||
case patch_type::le16: return "le16";
|
||||
case patch_type::le32: return "le32";
|
||||
case patch_type::le64: return "le64";
|
||||
case patch_type::bef32: return "bef32";
|
||||
case patch_type::bef64: return "bef64";
|
||||
case patch_type::be16: return "be16";
|
||||
case patch_type::be32: return "be32";
|
||||
case patch_type::be64: return "be64";
|
||||
case patch_type::lef32: return "lef32";
|
||||
case patch_type::lef64: return "lef64";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
void patch_engine::append(const std::string& patch)
|
||||
{
|
||||
if (fs::file f{patch})
|
||||
{
|
||||
YAML::Node root;
|
||||
|
||||
try
|
||||
{
|
||||
root = YAML::Load(f.to_string());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG_FATAL(GENERAL, "Failed to load patch file %s\n%s thrown: %s", patch, typeid(e).name(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto pair : root)
|
||||
{
|
||||
auto& name = pair.first.Scalar();
|
||||
auto& data = m_map[name];
|
||||
|
||||
for (auto patch : pair.second)
|
||||
{
|
||||
u64 type64 = 0;
|
||||
cfg::try_to_enum_value(&type64, &fmt_class_string<patch_type>::format, patch[0].Scalar());
|
||||
|
||||
struct patch info{};
|
||||
info.type = static_cast<patch_type>(type64);
|
||||
info.offset = patch[1].as<u32>(0);
|
||||
|
||||
switch (info.type)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// Special syntax: copy named sequence (must be loaded before)
|
||||
const auto found = m_map.find(patch[1].Scalar());
|
||||
|
||||
if (found != m_map.end())
|
||||
{
|
||||
// Address modifier (optional)
|
||||
const u32 mod = patch[2].as<u32>(0);
|
||||
|
||||
for (const auto& rd : found->second)
|
||||
{
|
||||
info = rd;
|
||||
info.offset += mod;
|
||||
data.emplace_back(info);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: error
|
||||
break;
|
||||
}
|
||||
case patch_type::bef32:
|
||||
case patch_type::lef32:
|
||||
{
|
||||
info.value_as<f32>() = patch[2].as<f32>();
|
||||
break;
|
||||
}
|
||||
case patch_type::bef64:
|
||||
case patch_type::lef64:
|
||||
{
|
||||
info.value_as<f64>() = patch[2].as<f64>();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
info.value = patch[2].as<u64>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data.emplace_back(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t patch_engine::apply(const std::string& name, u8* dst) const
|
||||
{
|
||||
const auto found = m_map.find(name);
|
||||
|
||||
if (found == m_map.cend())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply modifications sequentially
|
||||
for (const auto& p : found->second)
|
||||
{
|
||||
auto ptr = dst + p.offset;
|
||||
|
||||
switch (p.type)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// Invalid in this context
|
||||
break;
|
||||
}
|
||||
case patch_type::byte:
|
||||
{
|
||||
*ptr = static_cast<u8>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le16:
|
||||
{
|
||||
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le32:
|
||||
case patch_type::lef32:
|
||||
{
|
||||
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le64:
|
||||
case patch_type::lef64:
|
||||
{
|
||||
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be16:
|
||||
{
|
||||
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be32:
|
||||
case patch_type::bef32:
|
||||
{
|
||||
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be64:
|
||||
case patch_type::bef64:
|
||||
{
|
||||
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found->second.size();
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BEType.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
enum class patch_type
|
||||
{
|
||||
load,
|
||||
byte,
|
||||
le16,
|
||||
le32,
|
||||
le64,
|
||||
lef32,
|
||||
lef64,
|
||||
be16,
|
||||
be32,
|
||||
be64,
|
||||
bef32,
|
||||
bef64,
|
||||
};
|
||||
|
||||
class patch_engine
|
||||
{
|
||||
struct patch
|
||||
{
|
||||
patch_type type;
|
||||
u32 offset;
|
||||
u64 value;
|
||||
|
||||
template <typename T>
|
||||
T& value_as()
|
||||
{
|
||||
return *reinterpret_cast<T*>(reinterpret_cast<char*>(&value));
|
||||
}
|
||||
};
|
||||
|
||||
// Database
|
||||
std::unordered_map<std::string, std::vector<patch>> m_map;
|
||||
|
||||
public:
|
||||
// Load from file
|
||||
void append(const std::string& path);
|
||||
|
||||
// Apply patch (returns the number of entries applied)
|
||||
std::size_t apply(const std::string& name, u8* dst) const;
|
||||
};
|
||||
@@ -1,732 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
This header helps to extend scoped enum types (enum class) in two possible ways:
|
||||
1) Enabling bitwise operators for enums
|
||||
2) Advanced bs_t<> template (this converts enum type to another "bitset" enum type)
|
||||
|
||||
To enable bitwise operators, enum scope must contain `__bitwise_ops` entry.
|
||||
|
||||
enum class flags
|
||||
{
|
||||
__bitwise_ops, // Not essential, but recommended to put it first
|
||||
|
||||
flag1 = 1 << 0,
|
||||
flag2 = 1 << 1,
|
||||
};
|
||||
|
||||
Examples:
|
||||
`flags::flag1 | flags::flag2` - bitwise OR
|
||||
`flags::flag1 & flags::flag2` - bitwise AND
|
||||
`flags::flag1 ^ flags::flag2` - bitwise XOR
|
||||
`~flags::flag1` - bitwise NEG
|
||||
|
||||
To enable bs_t<> template, enum scope must contain `__bitset_enum_max` entry.
|
||||
|
||||
enum class flagzz : u32
|
||||
{
|
||||
flag1, // Bit indices start from zero
|
||||
flag2,
|
||||
|
||||
__bitset_enum_max // It must be the last value
|
||||
};
|
||||
|
||||
Now some operators are enabled for two enum types: `flagzz` and `bs_t<flagzz>`.
|
||||
These are very different from previously described bitwise operators.
|
||||
|
||||
Examples:
|
||||
`+flagzz::flag1` - unary `+` operator convert flagzz value to bs_t<flagzz>
|
||||
`flagzz::flag1 + flagzz::flag2` - bitset union
|
||||
`flagzz::flag1 - flagzz::flag2` - bitset difference
|
||||
Intersection (&) and symmetric difference (^) is also available.
|
||||
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Helper template
|
||||
template<typename T>
|
||||
struct bs_base
|
||||
{
|
||||
// Underlying type
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
// Actual bitset type
|
||||
enum class type : under
|
||||
{
|
||||
null = 0, // Empty bitset
|
||||
|
||||
__bitset_set_type = 0 // SFINAE marker
|
||||
};
|
||||
|
||||
static constexpr std::size_t bitmax = sizeof(T) * 8;
|
||||
static constexpr std::size_t bitsize = static_cast<under>(T::__bitset_enum_max);
|
||||
|
||||
static_assert(std::is_enum<T>::value, "bs_t<> error: invalid type (must be enum)");
|
||||
static_assert(!bitsize || bitsize <= bitmax, "bs_t<> error: invalid __bitset_enum_max");
|
||||
|
||||
// Helper function
|
||||
static constexpr under shift(T value)
|
||||
{
|
||||
return static_cast<under>(1) << static_cast<under>(value);
|
||||
}
|
||||
|
||||
friend type& operator +=(type& lhs, type rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) |= static_cast<under>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator -=(type& lhs, type rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) &= ~static_cast<under>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator &=(type& lhs, type rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) &= static_cast<under>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator ^=(type& lhs, type rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) ^= static_cast<under>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator +=(type& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) |= shift(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator -=(type& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) &= ~shift(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator &=(type& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) &= shift(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend type& operator ^=(type& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<under&>(lhs) ^= shift(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
friend constexpr type operator +(type lhs, type rhs)
|
||||
{
|
||||
return static_cast<type>(static_cast<under>(lhs) | static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator -(type lhs, type rhs)
|
||||
{
|
||||
return static_cast<type>(static_cast<under>(lhs) & ~static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator &(type lhs, type rhs)
|
||||
{
|
||||
return static_cast<type>(static_cast<under>(lhs) & static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator ^(type lhs, type rhs)
|
||||
{
|
||||
return static_cast<type>(static_cast<under>(lhs) ^ static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator &(type lhs, T rhs)
|
||||
{
|
||||
return static_cast<type>(static_cast<under>(lhs) & shift(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator ^(type lhs, T rhs)
|
||||
{
|
||||
return static_cast<type>(static_cast<under>(lhs) ^ shift(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator &(T lhs, type rhs)
|
||||
{
|
||||
return static_cast<type>(shift(lhs) & static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend constexpr type operator ^(T lhs, type rhs)
|
||||
{
|
||||
return static_cast<type>(shift(lhs) ^ static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend constexpr bool operator ==(T lhs, type rhs)
|
||||
{
|
||||
return shift(lhs) == rhs;
|
||||
}
|
||||
|
||||
friend constexpr bool operator ==(type lhs, T rhs)
|
||||
{
|
||||
return lhs == shift(rhs);
|
||||
}
|
||||
|
||||
friend constexpr bool operator !=(T lhs, type rhs)
|
||||
{
|
||||
return shift(lhs) != rhs;
|
||||
}
|
||||
|
||||
friend constexpr bool operator !=(type lhs, T rhs)
|
||||
{
|
||||
return lhs != shift(rhs);
|
||||
}
|
||||
|
||||
friend constexpr bool test(type value)
|
||||
{
|
||||
return static_cast<under>(value) != 0;
|
||||
}
|
||||
|
||||
friend constexpr bool test(type lhs, type rhs)
|
||||
{
|
||||
return (static_cast<under>(lhs) & static_cast<under>(rhs)) != 0;
|
||||
}
|
||||
|
||||
friend constexpr bool test(type lhs, T rhs)
|
||||
{
|
||||
return (static_cast<under>(lhs) & shift(rhs)) != 0;
|
||||
}
|
||||
|
||||
friend constexpr bool test(T lhs, type rhs)
|
||||
{
|
||||
return (shift(lhs) & static_cast<under>(rhs)) != 0;
|
||||
}
|
||||
|
||||
friend bool test_and_set(type& lhs, type rhs)
|
||||
{
|
||||
return test_and_set(reinterpret_cast<under&>(lhs), static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend bool test_and_set(type& lhs, T rhs)
|
||||
{
|
||||
return test_and_set(reinterpret_cast<under&>(lhs), shift(rhs));
|
||||
}
|
||||
|
||||
friend bool test_and_reset(type& lhs, type rhs)
|
||||
{
|
||||
return test_and_reset(reinterpret_cast<under&>(lhs), static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend bool test_and_reset(type& lhs, T rhs)
|
||||
{
|
||||
return test_and_reset(reinterpret_cast<under&>(lhs), shift(rhs));
|
||||
}
|
||||
|
||||
friend bool test_and_complement(type& lhs, type rhs)
|
||||
{
|
||||
return test_and_complement(reinterpret_cast<under&>(lhs), static_cast<under>(rhs));
|
||||
}
|
||||
|
||||
friend bool test_and_complement(type& lhs, T rhs)
|
||||
{
|
||||
return test_and_complement(reinterpret_cast<under&>(lhs), shift(rhs));
|
||||
}
|
||||
};
|
||||
|
||||
// Bitset type for enum class with available bits [0, T::__bitset_enum_max)
|
||||
template<typename T>
|
||||
using bs_t = typename bs_base<T>::type;
|
||||
|
||||
// Unary '+' operator: promote plain enum value to bitset value
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator +(T value)
|
||||
{
|
||||
return static_cast<bs_t<T>>(bs_base<T>::shift(value));
|
||||
}
|
||||
|
||||
// Binary '+' operator: bitset union
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator +(T lhs, T rhs)
|
||||
{
|
||||
return static_cast<bs_t<T>>(bs_base<T>::shift(lhs) | bs_base<T>::shift(rhs));
|
||||
}
|
||||
|
||||
// Binary '+' operator: bitset union
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator +(typename bs_base<T>::type lhs, T rhs)
|
||||
{
|
||||
return static_cast<bs_t<T>>(static_cast<typename bs_base<T>::under>(lhs) | bs_base<T>::shift(rhs));
|
||||
}
|
||||
|
||||
// Binary '+' operator: bitset union
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator +(T lhs, typename bs_base<T>::type rhs)
|
||||
{
|
||||
return static_cast<bs_t<T>>(bs_base<T>::shift(lhs) | static_cast<typename bs_base<T>::under>(rhs));
|
||||
}
|
||||
|
||||
// Binary '-' operator: bitset difference
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator -(T lhs, T rhs)
|
||||
{
|
||||
return static_cast<bs_t<T>>(bs_base<T>::shift(lhs) & ~bs_base<T>::shift(rhs));
|
||||
}
|
||||
|
||||
// Binary '-' operator: bitset difference
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator -(typename bs_base<T>::type lhs, T rhs)
|
||||
{
|
||||
return static_cast<bs_t<T>>(static_cast<typename bs_base<T>::under>(lhs) & ~bs_base<T>::shift(rhs));
|
||||
}
|
||||
|
||||
// Binary '-' operator: bitset difference
|
||||
template<typename T, typename = decltype(T::__bitset_enum_max)>
|
||||
constexpr bs_t<T> operator -(T lhs, typename bs_base<T>::type rhs)
|
||||
{
|
||||
return static_cast<bs_t<T>>(bs_base<T>::shift(lhs) & ~static_cast<typename bs_base<T>::under>(rhs));
|
||||
}
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_add<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bs_t<T> op1(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::fetch_or(reinterpret_cast<under&>(left), bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline bs_t<T> op2(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::or_fetch(reinterpret_cast<under&>(left), bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_sub<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bs_t<T> op1(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::fetch_and(reinterpret_cast<under&>(left), ~bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline bs_t<T> op2(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::and_fetch(reinterpret_cast<under&>(left), ~bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_and<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bs_t<T> op1(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::fetch_and(reinterpret_cast<under&>(left), bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline bs_t<T> op2(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::and_fetch(reinterpret_cast<under&>(left), bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_xor<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bs_t<T> op1(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::fetch_xor(reinterpret_cast<under&>(left), bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline bs_t<T> op2(bs_t<T>& left, T right)
|
||||
{
|
||||
return static_cast<bs_t<T>>(atomic_storage<under>::xor_fetch(reinterpret_cast<under&>(left), bs_base<T>::shift(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_add<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_or(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::or_fetch(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_sub<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_and(reinterpret_cast<under&>(left), ~static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::and_fetch(reinterpret_cast<under&>(left), ~static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_and<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_and(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::and_fetch(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_xor<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_xor(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::xor_fetch(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_test_and_set<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bool _op(bs_t<T>& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::bts(reinterpret_cast<under&>(left), static_cast<uint>(static_cast<under>(value)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_test_and_reset<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bool _op(bs_t<T>& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::btr(reinterpret_cast<under&>(left), static_cast<uint>(static_cast<under>(value)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename BS, typename T>
|
||||
struct atomic_test_and_complement<BS, T, void_t<decltype(T::__bitset_enum_max), std::enable_if_t<std::is_same<BS, bs_t<T>>::value>>>
|
||||
{
|
||||
using under = typename bs_base<T>::under;
|
||||
|
||||
static inline bool _op(bs_t<T>& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::btc(reinterpret_cast<under&>(left), static_cast<uint>(static_cast<under>(value)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_test_and_set<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline bool _op(T& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::test_and_set(reinterpret_cast<under&>(left), static_cast<under>(value));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_test_and_reset<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline bool _op(T& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::test_and_reset(reinterpret_cast<under&>(left), static_cast<under>(value));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_test_and_complement<T, T, void_t<decltype(T::__bitset_set_type)>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline bool _op(T& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::test_and_complement(reinterpret_cast<under&>(left), static_cast<under>(value));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
// Binary '|' operator: bitwise OR
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
constexpr T operator |(T lhs, T rhs)
|
||||
{
|
||||
return static_cast<T>(std::underlying_type_t<T>(lhs) | std::underlying_type_t<T>(rhs));
|
||||
}
|
||||
|
||||
// Binary '&' operator: bitwise AND
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
constexpr T operator &(T lhs, T rhs)
|
||||
{
|
||||
return static_cast<T>(std::underlying_type_t<T>(lhs) & std::underlying_type_t<T>(rhs));
|
||||
}
|
||||
|
||||
// Binary '^' operator: bitwise XOR
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
constexpr T operator ^(T lhs, T rhs)
|
||||
{
|
||||
return static_cast<T>(std::underlying_type_t<T>(lhs) ^ std::underlying_type_t<T>(rhs));
|
||||
}
|
||||
|
||||
// Unary '~' operator: bitwise NEG
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
constexpr T operator ~(T value)
|
||||
{
|
||||
return static_cast<T>(~std::underlying_type_t<T>(value));
|
||||
}
|
||||
|
||||
// Bitwise OR assignment
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
inline T& operator |=(T& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<std::underlying_type_t<T>&>(lhs) |= std::underlying_type_t<T>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
// Bitwise AND assignment
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
inline T& operator &=(T& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<std::underlying_type_t<T>&>(lhs) &= std::underlying_type_t<T>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
// Bitwise XOR assignment
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
inline T& operator ^=(T& lhs, T rhs)
|
||||
{
|
||||
reinterpret_cast<std::underlying_type_t<T>&>(lhs) ^= std::underlying_type_t<T>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
constexpr bool test(T value)
|
||||
{
|
||||
return std::underlying_type_t<T>(value) != 0;
|
||||
}
|
||||
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
constexpr bool test(T lhs, T rhs)
|
||||
{
|
||||
return (std::underlying_type_t<T>(lhs) & std::underlying_type_t<T>(rhs)) != 0;
|
||||
}
|
||||
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
inline bool test_and_set(T& lhs, T rhs)
|
||||
{
|
||||
return test_and_set(reinterpret_cast<std::underlying_type_t<T>&>(lhs), std::underlying_type_t<T>(rhs));
|
||||
}
|
||||
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
inline bool test_and_reset(T& lhs, T rhs)
|
||||
{
|
||||
return test_and_reset(reinterpret_cast<std::underlying_type_t<T>&>(lhs), std::underlying_type_t<T>(rhs));
|
||||
}
|
||||
|
||||
template<typename T, typename = decltype(T::__bitwise_ops)>
|
||||
inline bool test_and_complement(T& lhs, T rhs)
|
||||
{
|
||||
return test_and_complement(reinterpret_cast<std::underlying_type_t<T>&>(lhs), std::underlying_type_t<T>(rhs));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct atomic_or<T, T, void_t<decltype(T::__bitwise_ops), std::enable_if_t<std::is_enum<T>::value>>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_or(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::or_fetch(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_and<T, T, void_t<decltype(T::__bitwise_ops), std::enable_if_t<std::is_enum<T>::value>>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_and(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::and_fetch(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_xor<T, T, void_t<decltype(T::__bitwise_ops), std::enable_if_t<std::is_enum<T>::value>>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline T op1(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::fetch_xor(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &op1;
|
||||
|
||||
static inline T op2(T& left, T right)
|
||||
{
|
||||
return static_cast<T>(atomic_storage<under>::xor_fetch(reinterpret_cast<under&>(left), static_cast<under>(right)));
|
||||
}
|
||||
|
||||
static constexpr auto op_fetch = &op2;
|
||||
static constexpr auto atomic_op = &op2;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_test_and_set<T, T, void_t<decltype(T::__bitwise_ops), std::enable_if_t<std::is_enum<T>::value>>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline bool _op(T& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::test_and_set(reinterpret_cast<under&>(left), static_cast<under>(value));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_test_and_reset<T, T, void_t<decltype(T::__bitwise_ops), std::enable_if_t<std::is_enum<T>::value>>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline bool _op(T& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::test_and_reset(reinterpret_cast<under&>(left), static_cast<under>(value));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct atomic_test_and_complement<T, T, void_t<decltype(T::__bitwise_ops), std::enable_if_t<std::is_enum<T>::value>>>
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
|
||||
static inline bool _op(T& left, T value)
|
||||
{
|
||||
return atomic_storage<under>::test_and_complement(reinterpret_cast<under&>(left), static_cast<under>(value));
|
||||
}
|
||||
|
||||
static constexpr auto fetch_op = &_op;
|
||||
static constexpr auto op_fetch = &_op;
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
@@ -1,634 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include <climits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
/*
|
||||
C-style format parser. Appends formatted string to `out`, returns number of characters written.
|
||||
`out`: mutable reference to std::string, std::vector<char> or other compatible container
|
||||
`fmt`: null-terminated string of `Char` type (char or constructible from char)
|
||||
`src`: rvalue reference to argument provider.
|
||||
*/
|
||||
template<typename Dst, typename Char, typename Src>
|
||||
std::size_t cfmt_append(Dst& out, const Char* fmt, Src&& src)
|
||||
{
|
||||
const std::size_t start_pos = out.size();
|
||||
|
||||
struct cfmt_context
|
||||
{
|
||||
std::size_t size; // Size of current format sequence
|
||||
|
||||
u8 args; // Number of extra args used
|
||||
u8 type; // Integral type bytesize
|
||||
bool dot; // Precision enabled
|
||||
bool left;
|
||||
bool sign;
|
||||
bool space;
|
||||
bool alter;
|
||||
bool zeros;
|
||||
|
||||
uint width;
|
||||
uint prec;
|
||||
};
|
||||
|
||||
cfmt_context ctx{0};
|
||||
|
||||
// Error handling: print untouched sequence, stop further formatting
|
||||
const auto drop_sequence = [&]
|
||||
{
|
||||
out.insert(out.end(), fmt - ctx.size, fmt);
|
||||
ctx.size = -1;
|
||||
};
|
||||
|
||||
const auto read_decimal = [&](uint result) -> uint
|
||||
{
|
||||
while (fmt[0] >= '0' && fmt[0] <= '9' && result <= (UINT_MAX / 10))
|
||||
{
|
||||
result = result * 10 + (fmt[0] - '0');
|
||||
fmt++, ctx.size++;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto write_octal = [&](u64 value, u64 min_num)
|
||||
{
|
||||
out.resize(out.size() + std::max<u64>(min_num, 66 / 3 - (cntlz64(value | 1, true) + 2) / 3), '0');
|
||||
|
||||
// Write in reversed order
|
||||
for (auto i = out.rbegin(); value; i++, value /= 8)
|
||||
{
|
||||
*i = value % 8 + '0';
|
||||
}
|
||||
};
|
||||
|
||||
const auto write_hex = [&](u64 value, bool upper, u64 min_num)
|
||||
{
|
||||
out.resize(out.size() + std::max<u64>(min_num, 64 / 4 - cntlz64(value | 1, true) / 4), '0');
|
||||
|
||||
// Write in reversed order
|
||||
for (auto i = out.rbegin(); value; i++, value /= 16)
|
||||
{
|
||||
*i = (upper ? "0123456789ABCDEF" : "0123456789abcdef")[value % 16];
|
||||
}
|
||||
};
|
||||
|
||||
const auto write_decimal = [&](u64 value, s64 min_size)
|
||||
{
|
||||
const std::size_t start = out.size();
|
||||
|
||||
do
|
||||
{
|
||||
out.push_back(value % 10 + '0');
|
||||
value /= 10;
|
||||
}
|
||||
while (0 < --min_size || value);
|
||||
|
||||
// Revert written characters
|
||||
for (std::size_t i = start, j = out.size() - 1; i < j; i++, j--)
|
||||
{
|
||||
std::swap(out[i], out[j]);
|
||||
}
|
||||
};
|
||||
|
||||
// Single pass over fmt string (null-terminated), TODO: check correct order
|
||||
while (const Char ch = *fmt++) if (ctx.size == 0)
|
||||
{
|
||||
if (ch == '%')
|
||||
{
|
||||
ctx.size = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.push_back(ch);
|
||||
}
|
||||
}
|
||||
else if (ctx.size == 1 && ch == '%')
|
||||
{
|
||||
ctx = {0};
|
||||
out.push_back(ch);
|
||||
}
|
||||
else if (ctx.size == -1)
|
||||
{
|
||||
out.push_back(ch);
|
||||
}
|
||||
else switch (ctx.size++, ch)
|
||||
{
|
||||
case '-': ctx.left = true; break;
|
||||
case '+': ctx.sign = true; break;
|
||||
case ' ': ctx.space = true; break;
|
||||
case '#': ctx.alter = true; break;
|
||||
case '0': ctx.zeros = true; break;
|
||||
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
{
|
||||
if (UNLIKELY(ctx.width))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.width = read_decimal(ch - '0');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case '*':
|
||||
{
|
||||
if (UNLIKELY(ctx.width || !src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
const int warg = src.template get<int>(ctx.args++);
|
||||
ctx.width = std::abs(warg);
|
||||
ctx.left |= warg < 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case '.':
|
||||
{
|
||||
if (UNLIKELY(ctx.dot || ctx.prec))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else if (*fmt >= '0' && *fmt <= '9') // TODO: does it allow '0'?
|
||||
{
|
||||
ctx.prec = read_decimal(0);
|
||||
ctx.dot = true;
|
||||
}
|
||||
else if (*fmt == '*')
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt++, ctx.size++;
|
||||
const int parg = src.template get<int>(ctx.args++);
|
||||
ctx.prec = std::max(parg, 0);
|
||||
ctx.dot = parg >= 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.prec = 0;
|
||||
ctx.dot = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'h':
|
||||
{
|
||||
if (UNLIKELY(ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else if (fmt[0] == 'h')
|
||||
{
|
||||
fmt++, ctx.size++;
|
||||
ctx.type = src.size_char;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = src.size_short;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'l':
|
||||
{
|
||||
if (UNLIKELY(ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else if (fmt[0] == 'l')
|
||||
{
|
||||
fmt++, ctx.size++;
|
||||
ctx.type = src.size_llong;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = src.size_long;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'z':
|
||||
{
|
||||
if (UNLIKELY(ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = src.size_size;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'j':
|
||||
{
|
||||
if (UNLIKELY(ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = src.size_max;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 't':
|
||||
{
|
||||
if (UNLIKELY(ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = src.size_diff;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'c':
|
||||
{
|
||||
if (UNLIKELY(ctx.type || !src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t start = out.size();
|
||||
out.push_back(src.template get<Char>(ctx.args));
|
||||
|
||||
if (1 < ctx.width)
|
||||
{
|
||||
// Add spaces if necessary
|
||||
out.insert(out.begin() + start + ctx.left, ctx.width - 1, ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 's':
|
||||
{
|
||||
if (UNLIKELY(ctx.type || !src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t start = out.size();
|
||||
const std::size_t size1 = src.fmt_string(out, ctx.args);
|
||||
|
||||
if (ctx.dot && size1 > ctx.prec)
|
||||
{
|
||||
// Shrink if necessary
|
||||
out.resize(start + ctx.prec);
|
||||
}
|
||||
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add spaces if necessary
|
||||
out.insert(ctx.left ? out.end() : out.begin() + start, ctx.width - size2, ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'd':
|
||||
case 'i':
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = (u8)src.type(ctx.args);
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = src.size_int;
|
||||
}
|
||||
}
|
||||
|
||||
// Sign-extended argument expected
|
||||
const u64 val = src.template get<u64>(ctx.args);
|
||||
const bool negative = ctx.type && static_cast<s64>(val) < 0;
|
||||
|
||||
const std::size_t start = out.size();
|
||||
|
||||
if (!ctx.dot || ctx.prec)
|
||||
{
|
||||
if (negative)
|
||||
{
|
||||
out.push_back('-');
|
||||
}
|
||||
else if (ctx.sign)
|
||||
{
|
||||
out.push_back('+');
|
||||
}
|
||||
else if (ctx.space)
|
||||
{
|
||||
out.push_back(' ');
|
||||
}
|
||||
|
||||
write_decimal(negative ? 0 - val : val, ctx.prec);
|
||||
}
|
||||
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add padding if necessary
|
||||
if (ctx.zeros && !ctx.left && !ctx.dot)
|
||||
{
|
||||
out.insert(out.begin() + start + (negative || ctx.sign || ctx.space), ctx.width - size2, '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
out.insert(ctx.left ? out.end() : out.begin() + start, ctx.width - size2, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'o':
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = (u8)src.type(ctx.args);
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = src.size_int;
|
||||
}
|
||||
}
|
||||
|
||||
const u64 mask =
|
||||
ctx.type == 1 ? 0xffull :
|
||||
ctx.type == 2 ? 0xffffull :
|
||||
ctx.type == 4 ? 0xffffffffull : 0xffffffffffffffffull;
|
||||
|
||||
// Trunc sign-extended signed types
|
||||
const u64 val = src.template get<u64>(ctx.args) & mask;
|
||||
|
||||
const std::size_t start = out.size();
|
||||
|
||||
if (ctx.alter)
|
||||
{
|
||||
out.push_back('0');
|
||||
|
||||
if (val)
|
||||
{
|
||||
write_octal(val, ctx.prec ? ctx.prec - 1 : 0);
|
||||
}
|
||||
}
|
||||
else if (!ctx.dot || ctx.prec)
|
||||
{
|
||||
write_octal(val, ctx.prec);
|
||||
}
|
||||
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add padding if necessary
|
||||
out.insert(ctx.left ? out.end() : out.begin() + start, ctx.width - size2, ctx.zeros && !ctx.left && !ctx.dot ? '0' : ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'x':
|
||||
case 'X':
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = (u8)src.type(ctx.args);
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = src.size_int;
|
||||
}
|
||||
}
|
||||
|
||||
const u64 mask =
|
||||
ctx.type == 1 ? 0xffull :
|
||||
ctx.type == 2 ? 0xffffull :
|
||||
ctx.type == 4 ? 0xffffffffull : 0xffffffffffffffffull;
|
||||
|
||||
// Trunc sign-extended signed types
|
||||
const u64 val = src.template get<u64>(ctx.args) & mask;
|
||||
|
||||
const std::size_t start = out.size();
|
||||
|
||||
if (ctx.alter)
|
||||
{
|
||||
out.push_back('0');
|
||||
|
||||
if (val)
|
||||
{
|
||||
out.push_back(ch); // Prepend 0x or 0X
|
||||
write_hex(val, ch == 'X', ctx.prec);
|
||||
}
|
||||
}
|
||||
else if (!ctx.dot || ctx.prec)
|
||||
{
|
||||
write_hex(val, ch == 'X', ctx.prec);
|
||||
}
|
||||
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add padding if necessary
|
||||
if (ctx.zeros && !ctx.left && !ctx.dot)
|
||||
{
|
||||
out.insert(out.begin() + start + (ctx.alter && val ? 2 : 0), ctx.width - size2, '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
out.insert(ctx.left ? out.end() : out.begin() + start, ctx.width - size2, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'u':
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args)))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = (u8)src.type(ctx.args);
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = src.size_int;
|
||||
}
|
||||
}
|
||||
|
||||
const u64 mask =
|
||||
ctx.type == 1 ? 0xffull :
|
||||
ctx.type == 2 ? 0xffffull :
|
||||
ctx.type == 4 ? 0xffffffffull : 0xffffffffffffffffull;
|
||||
|
||||
// Trunc sign-extended signed types
|
||||
const u64 val = src.template get<u64>(ctx.args) & mask;
|
||||
|
||||
const std::size_t start = out.size();
|
||||
|
||||
if (!ctx.dot || ctx.prec)
|
||||
{
|
||||
write_decimal(val, ctx.prec);
|
||||
}
|
||||
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add padding if necessary
|
||||
out.insert(ctx.left ? out.end() : out.begin() + start, ctx.width - size2, ctx.zeros && !ctx.left && !ctx.dot ? '0' : ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args) || ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
const u64 val = src.template get<u64>(ctx.args);
|
||||
|
||||
const std::size_t start = out.size();
|
||||
|
||||
write_hex(val, false, sizeof(void*) * 2);
|
||||
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add padding if necessary
|
||||
out.insert(ctx.left ? out.end() : out.begin() + start, ctx.width - size2, ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'a':
|
||||
case 'A':
|
||||
case 'g':
|
||||
case 'G':
|
||||
{
|
||||
if (UNLIKELY(!src.test(ctx.args) || ctx.type))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
// Fallback (TODO)
|
||||
|
||||
const std::string _fmt(fmt - ctx.size, fmt);
|
||||
|
||||
const f64 arg0 = src.template get<f64>(0);
|
||||
const u64 arg1 = ctx.args >= 1 ? src.template get<u64>(1) : 0;
|
||||
const u64 arg2 = ctx.args >= 2 ? src.template get<u64>(2) : 0;
|
||||
|
||||
if (const std::size_t _size = std::snprintf(0, 0, _fmt.c_str(), arg0, arg1, arg2))
|
||||
{
|
||||
out.resize(out.size() + _size);
|
||||
std::snprintf(&out.front() + out.size() - _size, _size + 1, _fmt.c_str(), arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'L': // long double, not supported
|
||||
case 'n': // writeback, not supported
|
||||
default:
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle unfinished sequence
|
||||
if (ctx.size && ctx.size != -1)
|
||||
{
|
||||
fmt--, drop_sequence();
|
||||
}
|
||||
|
||||
return out.size() - start_pos;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
#include "cond.h"
|
||||
#include "sync.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
bool cond_variable::imp_wait(u32 _old, u64 _timeout) noexcept
|
||||
{
|
||||
verify(HERE), _old != -1; // Very unlikely: it requires 2^32 distinct threads to wait simultaneously
|
||||
const bool is_inf = _timeout > max_timeout;
|
||||
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER timeout;
|
||||
timeout.QuadPart = _timeout * -10;
|
||||
|
||||
if (HRESULT rc = NtWaitForKeyedEvent(nullptr, &m_value, false, is_inf ? nullptr : &timeout))
|
||||
{
|
||||
verify(HERE), rc == WAIT_TIMEOUT;
|
||||
|
||||
// Retire
|
||||
if (!m_value.fetch_op([](u32& value) { if (value) value--; }))
|
||||
{
|
||||
NtWaitForKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
timespec timeout;
|
||||
timeout.tv_sec = _timeout / 1000000;
|
||||
timeout.tv_nsec = (_timeout % 1000000) * 1000;
|
||||
|
||||
for (u32 value = _old + 1;; value = m_value)
|
||||
{
|
||||
const int err = futex((int*)&m_value.raw(), FUTEX_WAIT_PRIVATE, value, is_inf ? nullptr : &timeout, nullptr, 0) == 0
|
||||
? 0
|
||||
: errno;
|
||||
|
||||
// Normal or timeout wakeup
|
||||
if (!err || (!is_inf && err == ETIMEDOUT))
|
||||
{
|
||||
// Cleanup (remove waiter)
|
||||
verify(HERE), m_value--;
|
||||
return !err;
|
||||
}
|
||||
|
||||
// Not a wakeup
|
||||
verify(HERE), err == EAGAIN;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cond_variable::imp_wake(u32 _count) noexcept
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Try to subtract required amount of waiters
|
||||
const u32 count = m_value.atomic_op([=](u32& value)
|
||||
{
|
||||
if (value > _count)
|
||||
{
|
||||
value -= _count;
|
||||
return _count;
|
||||
}
|
||||
|
||||
return std::exchange(value, 0);
|
||||
});
|
||||
|
||||
for (u32 i = count; i > 0; i--)
|
||||
{
|
||||
NtReleaseKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
}
|
||||
#else
|
||||
for (u32 i = _count; i > 0; std::this_thread::yield())
|
||||
{
|
||||
const u32 value = m_value;
|
||||
|
||||
// Constrain remaining amount with imaginary waiter count
|
||||
if (i > value)
|
||||
{
|
||||
i = value;
|
||||
}
|
||||
|
||||
if (!value || i == 0)
|
||||
{
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (const int res = futex((int*)&m_value.raw(), FUTEX_WAKE_PRIVATE, i > INT_MAX ? INT_MAX : i, nullptr, nullptr, 0))
|
||||
{
|
||||
verify(HERE), res >= 0 && (u32)res <= i;
|
||||
i -= res;
|
||||
}
|
||||
|
||||
if (!m_value || i == 0)
|
||||
{
|
||||
// Escape
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
|
||||
// Lightweight condition variable
|
||||
class cond_variable
|
||||
{
|
||||
// Internal waiter counter
|
||||
atomic_t<u32> m_value{0};
|
||||
|
||||
protected:
|
||||
// Internal waiting function
|
||||
bool imp_wait(u32 _old, u64 _timeout) noexcept;
|
||||
|
||||
// Try to notify up to _count threads
|
||||
void imp_wake(u32 _count) noexcept;
|
||||
|
||||
public:
|
||||
constexpr cond_variable() = default;
|
||||
|
||||
// Intrusive wait algorithm for lockable objects
|
||||
template <typename T>
|
||||
explicit_bool_t wait(T& object, u64 usec_timeout = -1)
|
||||
{
|
||||
const u32 _old = m_value.fetch_add(1); // Increment waiter counter
|
||||
object.unlock();
|
||||
const bool res = imp_wait(_old, usec_timeout);
|
||||
object.lock();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Wake one thread
|
||||
void notify_one() noexcept
|
||||
{
|
||||
if (m_value)
|
||||
{
|
||||
imp_wake(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Wake all threads
|
||||
void notify_all() noexcept
|
||||
{
|
||||
if (m_value)
|
||||
{
|
||||
imp_wake(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr u64 max_timeout = u64{UINT32_MAX} / 1000 * 1000000;
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
#include "stdafx.h"
|
||||
#include "config_context.h"
|
||||
#include "StrFmt.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
void config_context_t::group::init()
|
||||
{
|
||||
if(!m_cfg->m_groups[full_name()])
|
||||
m_cfg->m_groups[full_name()] = this;
|
||||
}
|
||||
|
||||
config_context_t::group::group(config_context_t* cfg, const std::string& name)
|
||||
: m_cfg(cfg)
|
||||
, m_name(name)
|
||||
, m_parent(nullptr)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
config_context_t::group::group(group* parent, const std::string& name)
|
||||
: m_cfg(parent->m_cfg)
|
||||
, m_name(name)
|
||||
, m_parent(parent)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void config_context_t::group::set_parent(config_context_t* cfg)
|
||||
{
|
||||
m_cfg = cfg;
|
||||
init();
|
||||
}
|
||||
|
||||
std::string config_context_t::group::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
std::string config_context_t::group::full_name() const
|
||||
{
|
||||
if (m_parent)
|
||||
return m_parent->full_name() + "/" + m_name;
|
||||
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void config_context_t::assign(const config_context_t& rhs)
|
||||
{
|
||||
for (auto &rhs_g : rhs.m_groups)
|
||||
{
|
||||
auto g = m_groups.at(rhs_g.first);
|
||||
|
||||
for (auto rhs_e : rhs_g.second->entries)
|
||||
{
|
||||
if (g->entries[rhs_e.first])
|
||||
g->entries[rhs_e.first]->value_from(rhs_e.second);
|
||||
else
|
||||
g->add_entry(rhs_e.first, rhs_e.second->string_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void config_context_t::deserialize(std::istream& stream)
|
||||
{
|
||||
set_defaults();
|
||||
|
||||
uint line_index = 0;
|
||||
std::string line;
|
||||
group *current_group = nullptr;
|
||||
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
++line_index;
|
||||
line = fmt::trim(line);
|
||||
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
if (line.front() == '[' && line.back() == ']')
|
||||
{
|
||||
std::string group_name = line.substr(1, line.length() - 2);
|
||||
|
||||
auto found = m_groups.find(group_name);
|
||||
|
||||
if (found == m_groups.end())
|
||||
{
|
||||
std::cerr << line_index << ": group '" << group_name << "' not exists. ignored" << std::endl;
|
||||
current_group = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
current_group = found->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current_group == nullptr)
|
||||
{
|
||||
std::cerr << line_index << ": line '" << line << "' ignored, no group." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto name_value = fmt::split(line, { "=" });
|
||||
switch (name_value.size())
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if (current_group->entries[fmt::trim(name_value[0])])
|
||||
current_group->entries[fmt::trim(name_value[0])]->string_value({});
|
||||
|
||||
else
|
||||
current_group->add_entry(fmt::trim(name_value[0]), std::string{});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cerr << line_index << ": line '" << line << "' has more than one symbol '='. used only first" << std::endl;
|
||||
case 2:
|
||||
{
|
||||
if (current_group->entries[fmt::trim(name_value[0])])
|
||||
current_group->entries[fmt::trim(name_value[0])]->string_value(fmt::trim(name_value[1]));
|
||||
|
||||
else
|
||||
current_group->add_entry(fmt::trim(name_value[0]), fmt::trim(name_value[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void config_context_t::serialize(std::ostream& stream) const
|
||||
{
|
||||
for (auto &g : m_groups)
|
||||
{
|
||||
stream << "[" + g.first + "]" << std::endl;
|
||||
|
||||
for (auto &e : g.second->entries)
|
||||
{
|
||||
stream << e.first << "=" << e.second->string_value() << std::endl;
|
||||
}
|
||||
|
||||
stream << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void config_context_t::set_defaults()
|
||||
{
|
||||
for (auto &g : m_groups)
|
||||
{
|
||||
for (auto &e : g.second->entries)
|
||||
{
|
||||
e.second->to_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string config_context_t::to_string() const
|
||||
{
|
||||
std::ostringstream result;
|
||||
|
||||
serialize(result);
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
void config_context_t::from_string(const std::string& str)
|
||||
{
|
||||
std::istringstream source(str);
|
||||
|
||||
deserialize(source);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include "convert.h"
|
||||
|
||||
class config_context_t
|
||||
{
|
||||
public:
|
||||
class entry_base;
|
||||
|
||||
protected:
|
||||
class group
|
||||
{
|
||||
group* m_parent;
|
||||
config_context_t* m_cfg;
|
||||
std::string m_name;
|
||||
std::vector<std::unique_ptr<entry_base>> m_entries;
|
||||
|
||||
void init();
|
||||
|
||||
public:
|
||||
std::unordered_map<std::string, entry_base *> entries;
|
||||
|
||||
group(config_context_t* cfg, const std::string& name);
|
||||
group(group* parent, const std::string& name);
|
||||
void set_parent(config_context_t* cfg);
|
||||
|
||||
std::string name() const;
|
||||
std::string full_name() const;
|
||||
|
||||
template<typename T>
|
||||
void add_entry(const std::string& name, const T& def_value)
|
||||
{
|
||||
m_entries.emplace_back(std::make_unique<entry<T>>(this, name, def_value));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get_entry_value(const std::string& name, const T& def_value)
|
||||
{
|
||||
if (!entries[name])
|
||||
add_entry(name, def_value);
|
||||
|
||||
return convert::to<T>(entries[name]->string_value());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void set_entry_value(const std::string& name, const T& value)
|
||||
{
|
||||
if (entries[name])
|
||||
entries[name]->string_value(convert::to<std::string>(value));
|
||||
|
||||
else
|
||||
add_entry(name, value);
|
||||
}
|
||||
|
||||
friend config_context_t;
|
||||
};
|
||||
|
||||
public:
|
||||
class entry_base
|
||||
{
|
||||
public:
|
||||
virtual std::string name() = 0;
|
||||
virtual void to_default() = 0;
|
||||
virtual std::string string_value() = 0;
|
||||
virtual void string_value(const std::string& value) = 0;
|
||||
virtual void value_from(const entry_base* rhs) = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class entry : public entry_base
|
||||
{
|
||||
T m_default_value;
|
||||
T m_value;
|
||||
group* m_parent;
|
||||
std::string m_name;
|
||||
|
||||
public:
|
||||
entry(group* parent, const std::string& name, const T& default_value)
|
||||
: m_parent(parent)
|
||||
, m_name(name)
|
||||
, m_default_value(default_value)
|
||||
, m_value(default_value)
|
||||
{
|
||||
if(!parent->entries[name])
|
||||
parent->entries[name] = this;
|
||||
}
|
||||
|
||||
T default_value() const
|
||||
{
|
||||
return m_default_value;
|
||||
}
|
||||
|
||||
T value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void value(const T& new_value)
|
||||
{
|
||||
m_value = new_value;
|
||||
}
|
||||
|
||||
std::string name() override
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void to_default() override
|
||||
{
|
||||
value(default_value());
|
||||
}
|
||||
|
||||
std::string string_value() override
|
||||
{
|
||||
return convert::to<std::string>(value());
|
||||
}
|
||||
|
||||
void string_value(const std::string &new_value) override
|
||||
{
|
||||
value(convert::to<T>(new_value));
|
||||
}
|
||||
|
||||
void value_from(const entry_base* rhs) override
|
||||
{
|
||||
value(static_cast<const entry*>(rhs)->value());
|
||||
}
|
||||
|
||||
entry& operator = (const T& new_value)
|
||||
{
|
||||
value(new_value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T2>
|
||||
entry& operator = (const T2& new_value)
|
||||
{
|
||||
value(static_cast<T>(new_value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator const T&() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, group*> m_groups;
|
||||
|
||||
public:
|
||||
config_context_t() = default;
|
||||
|
||||
void assign(const config_context_t& rhs);
|
||||
|
||||
void serialize(std::ostream& stream) const;
|
||||
void deserialize(std::istream& stream);
|
||||
|
||||
void set_defaults();
|
||||
|
||||
std::string to_string() const;
|
||||
void from_string(const std::string&);
|
||||
};
|
||||
@@ -0,0 +1,279 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "types.h"
|
||||
|
||||
namespace convert
|
||||
{
|
||||
template<typename ReturnType, typename FromType>
|
||||
struct to_impl_t;
|
||||
|
||||
template<typename Type>
|
||||
struct to_impl_t<Type, Type>
|
||||
{
|
||||
static Type func(const Type& value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, bool>
|
||||
{
|
||||
static std::string func(bool value)
|
||||
{
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<bool, std::string>
|
||||
{
|
||||
static bool func(const std::string& value)
|
||||
{
|
||||
return value == "true" ? true : value == "false" ? false : throw std::invalid_argument(__FUNCTION__);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, signed char>
|
||||
{
|
||||
static std::string func(signed char value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, unsigned char>
|
||||
{
|
||||
static std::string func(unsigned char value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, short>
|
||||
{
|
||||
static std::string func(short value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, unsigned short>
|
||||
{
|
||||
static std::string func(unsigned short value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, int>
|
||||
{
|
||||
static std::string func(int value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, unsigned int>
|
||||
{
|
||||
static std::string func(unsigned int value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, long>
|
||||
{
|
||||
static std::string func(long value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, unsigned long>
|
||||
{
|
||||
static std::string func(unsigned long value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, long long>
|
||||
{
|
||||
static std::string func(long long value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, unsigned long long>
|
||||
{
|
||||
static std::string func(unsigned long long value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, float>
|
||||
{
|
||||
static std::string func(float value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, double>
|
||||
{
|
||||
static std::string func(double value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, long double>
|
||||
{
|
||||
static std::string func(long double value)
|
||||
{
|
||||
return std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, size2i>
|
||||
{
|
||||
static std::string func(size2i value)
|
||||
{
|
||||
return std::to_string(value.width) + "x" + std::to_string(value.height);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<std::string, position2i>
|
||||
{
|
||||
static std::string func(position2i value)
|
||||
{
|
||||
return std::to_string(value.x) + ":" + std::to_string(value.y);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<int, std::string>
|
||||
{
|
||||
static int func(const std::string& value)
|
||||
{
|
||||
return std::stoi(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<unsigned int, std::string>
|
||||
{
|
||||
static unsigned int func(const std::string& value)
|
||||
{
|
||||
return (unsigned long)std::stoul(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<long, std::string>
|
||||
{
|
||||
static long func(const std::string& value)
|
||||
{
|
||||
return std::stol(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<unsigned long, std::string>
|
||||
{
|
||||
static unsigned long func(const std::string& value)
|
||||
{
|
||||
return std::stoul(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<long long, std::string>
|
||||
{
|
||||
static long long func(const std::string& value)
|
||||
{
|
||||
return std::stoll(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<unsigned long long, std::string>
|
||||
{
|
||||
static unsigned long long func(const std::string& value)
|
||||
{
|
||||
return std::stoull(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<float, std::string>
|
||||
{
|
||||
static float func(const std::string& value)
|
||||
{
|
||||
return std::stof(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<double, std::string>
|
||||
{
|
||||
static double func(const std::string& value)
|
||||
{
|
||||
return std::stod(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<long double, std::string>
|
||||
{
|
||||
static long double func(const std::string& value)
|
||||
{
|
||||
return std::stold(value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<size2i, std::string>
|
||||
{
|
||||
static size2i func(const std::string& value)
|
||||
{
|
||||
const auto& data = fmt::split(value, { "x" });
|
||||
return { std::stoi(data[0]), std::stoi(data[1]) };
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct to_impl_t<position2i, std::string>
|
||||
{
|
||||
static position2i func(const std::string& value)
|
||||
{
|
||||
const auto& data = fmt::split(value, { ":" });
|
||||
return { std::stoi(data[0]), std::stoi(data[1]) };
|
||||
}
|
||||
};
|
||||
|
||||
template<typename ReturnType, typename FromType>
|
||||
ReturnType to(FromType value)
|
||||
{
|
||||
return to_impl_t<std::remove_all_extents_t<ReturnType>, std::remove_all_extents_t<FromType>>::func(value);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "dynamic_library.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace utils
|
||||
{
|
||||
dynamic_library::dynamic_library(const std::string &path)
|
||||
{
|
||||
load(path);
|
||||
}
|
||||
|
||||
dynamic_library::~dynamic_library()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool dynamic_library::load(const std::string &path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_handle = LoadLibraryA(path.c_str());
|
||||
#else
|
||||
m_handle = dlopen(path.c_str(), RTLD_LAZY);
|
||||
#endif
|
||||
return loaded();
|
||||
}
|
||||
|
||||
void dynamic_library::close()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)m_handle);
|
||||
#else
|
||||
dlclose(m_handle);
|
||||
#endif
|
||||
m_handle = nullptr;
|
||||
}
|
||||
|
||||
void *dynamic_library::get_impl(const std::string &name) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (void*)GetProcAddress((HMODULE)m_handle, name.c_str());
|
||||
#else
|
||||
return dlsym(m_handle, (char *)name.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool dynamic_library::loaded() const
|
||||
{
|
||||
return !m_handle;
|
||||
}
|
||||
|
||||
dynamic_library::operator bool() const
|
||||
{
|
||||
return loaded();
|
||||
}
|
||||
|
||||
void* get_proc_address(const char* lib, const char* name)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA(lib), name));
|
||||
#else
|
||||
return dlsym(dlopen(lib, RTLD_NOLOAD), name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#include <string>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class dynamic_library
|
||||
{
|
||||
void *m_handle = nullptr;
|
||||
|
||||
public:
|
||||
dynamic_library() = default;
|
||||
dynamic_library(const std::string &path);
|
||||
|
||||
~dynamic_library();
|
||||
|
||||
bool load(const std::string &path);
|
||||
void close();
|
||||
|
||||
private:
|
||||
void *get_impl(const std::string &name) const;
|
||||
|
||||
public:
|
||||
template<typename Type = void>
|
||||
Type *get(const std::string &name) const
|
||||
{
|
||||
Type *result;
|
||||
*(void **)(&result) = get_impl(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
bool get(Type *&function, const std::string &name) const
|
||||
{
|
||||
*(void **)(&function) = get_impl(name);
|
||||
|
||||
return !!function;
|
||||
}
|
||||
|
||||
bool loaded() const;
|
||||
explicit operator bool() const;
|
||||
};
|
||||
|
||||
// (assume the lib is always loaded)
|
||||
void* get_proc_address(const char* lib, const char* name);
|
||||
|
||||
template <typename F>
|
||||
struct dynamic_import
|
||||
{
|
||||
static_assert(sizeof(F) == 0, "Invalid function type");
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct dynamic_import<R(Args...)>
|
||||
{
|
||||
R(*ptr)(Args...);
|
||||
const char* const lib;
|
||||
const char* const name;
|
||||
|
||||
// Constant initialization
|
||||
constexpr dynamic_import(const char* lib, const char* name)
|
||||
: ptr(nullptr)
|
||||
, lib(lib)
|
||||
, name(name)
|
||||
{
|
||||
}
|
||||
|
||||
// Caller
|
||||
R operator()(Args... args)
|
||||
{
|
||||
if (!ptr)
|
||||
{
|
||||
// TODO: atomic
|
||||
ptr = reinterpret_cast<R(*)(Args...)>(get_proc_address(lib, name));
|
||||
}
|
||||
|
||||
return ptr(args...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#define DYNAMIC_IMPORT(lib, name, ...) static utils::dynamic_import<__VA_ARGS__> name(lib, #name);
|
||||
@@ -1,193 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
|
||||
#include "Atomic.h"
|
||||
|
||||
template <typename T, T Mod = T::__state_enum_max, typename Under = std::underlying_type_t<T>>
|
||||
T operator ++(T& value, int)
|
||||
{
|
||||
return std::exchange(value, static_cast<T>(value < T{} || value >= Mod ? static_cast<Under>(0) : static_cast<Under>(value) + 1));
|
||||
}
|
||||
|
||||
template <typename T, T Mod = T::__state_enum_max, typename Under = std::underlying_type_t<T>>
|
||||
T operator --(T& value, int)
|
||||
{
|
||||
return std::exchange(value, static_cast<T>(value <= T{} || value >= static_cast<Under>(Mod) - 1 ? static_cast<Under>(Mod) - 1 : static_cast<Under>(value) - 1));
|
||||
}
|
||||
|
||||
template <typename T, typename CRT, std::size_t Size = static_cast<std::underlying_type_t<T>>(T::__state_enum_max)>
|
||||
class state_machine
|
||||
{
|
||||
using under = std::underlying_type_t<T>;
|
||||
using ftype = void(CRT::*)(T);
|
||||
|
||||
atomic_t<T> m_value;
|
||||
|
||||
template <std::size_t... Ind>
|
||||
static inline ftype transition_map(std::integer_sequence<std::size_t, Ind...>, T state)
|
||||
{
|
||||
// Constantly initialized list of functions
|
||||
static constexpr ftype map[Size]{&CRT::template transition<static_cast<T>(Ind)>...};
|
||||
|
||||
// Unsafe table lookup (TODO)
|
||||
return map[static_cast<under>(state)];
|
||||
}
|
||||
|
||||
// "Convert" variable argument to template argument
|
||||
static inline ftype transition_get(T state)
|
||||
{
|
||||
return transition_map(std::make_index_sequence<Size>(), state);
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr state_machine()
|
||||
: m_value{T{}}
|
||||
{
|
||||
}
|
||||
|
||||
constexpr state_machine(T state)
|
||||
: m_value{state}
|
||||
{
|
||||
}
|
||||
|
||||
// Get current state
|
||||
T state_get() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
// Unconditionally set state
|
||||
void state_set(T state)
|
||||
{
|
||||
T _old = m_value.exchange(state);
|
||||
|
||||
if (_old != state)
|
||||
{
|
||||
(static_cast<CRT*>(this)->*transition_get(state))(_old);
|
||||
}
|
||||
}
|
||||
|
||||
// Conditionally set state (optimized)
|
||||
explicit_bool_t state_test_and_set(T expected, T state)
|
||||
{
|
||||
if (m_value == expected && m_value.compare_and_swap_test(expected, state))
|
||||
{
|
||||
(static_cast<CRT*>(this)->*transition_get(state))(expected);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Conditionally set state (list version)
|
||||
explicit_bool_t state_test_and_set(std::initializer_list<T> expected, T state)
|
||||
{
|
||||
T _old;
|
||||
|
||||
if (m_value.atomic_op([&](T& value)
|
||||
{
|
||||
for (T x : expected)
|
||||
{
|
||||
if (value == x)
|
||||
{
|
||||
_old = std::exchange(value, state);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
(static_cast<CRT*>(this)->*transition_get(state))(_old);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unconditionally set next state
|
||||
void state_next()
|
||||
{
|
||||
T _old, state = m_value.op_fetch([&](T& value)
|
||||
{
|
||||
_old = value++;
|
||||
});
|
||||
|
||||
(static_cast<CRT*>(this)->*transition_get(state))(_old);
|
||||
}
|
||||
|
||||
// Unconditionally set previous state
|
||||
void state_prev()
|
||||
{
|
||||
T _old, state = m_value.op_fetch([&](T& value)
|
||||
{
|
||||
_old = value--;
|
||||
});
|
||||
|
||||
(static_cast<CRT*>(this)->*transition_get(state))(_old);
|
||||
}
|
||||
|
||||
// Get number of states
|
||||
static constexpr std::size_t size()
|
||||
{
|
||||
return Size;
|
||||
}
|
||||
};
|
||||
|
||||
//enum class test_state
|
||||
//{
|
||||
// on,
|
||||
// off,
|
||||
// la,
|
||||
//
|
||||
// __state_enum_max // 3
|
||||
//};
|
||||
//
|
||||
//struct test_machine final : state_machine<test_state, test_machine>
|
||||
//{
|
||||
// template <test_state>
|
||||
// void transition(test_state old_state);
|
||||
//
|
||||
// void on()
|
||||
// {
|
||||
// state_set(test_state::on);
|
||||
// }
|
||||
//
|
||||
// void off()
|
||||
// {
|
||||
// state_set(test_state::off);
|
||||
// }
|
||||
//
|
||||
// void test()
|
||||
// {
|
||||
// state_next();
|
||||
// }
|
||||
//};
|
||||
//
|
||||
//template <>
|
||||
//void test_machine::transition<test_state::on>(test_state)
|
||||
//{
|
||||
// LOG_SUCCESS(GENERAL, "ON");
|
||||
//}
|
||||
//
|
||||
//template <>
|
||||
//void test_machine::transition<test_state::off>(test_state)
|
||||
//{
|
||||
// LOG_SUCCESS(GENERAL, "OFF");
|
||||
//}
|
||||
//
|
||||
//
|
||||
//template <>
|
||||
//void test_machine::transition<test_state::la>(test_state)
|
||||
//{
|
||||
// on();
|
||||
// off();
|
||||
// test();
|
||||
//}
|
||||
|
||||
enum class event_result
|
||||
{
|
||||
skip,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,9 @@ rem // A copy of the GPL 2.0 should have been included with the program.
|
||||
rem // If not, see http://www.gnu.org/licenses/
|
||||
|
||||
rem // Official git repository and contact information can be found at
|
||||
rem // https://github.com/RPCS3/rpcs3 and https://rpcs3.net/.
|
||||
rem // https://github.com/RPCS3/rpcs3 and http://rpcs3.net/.
|
||||
|
||||
setlocal ENABLEDELAYEDEXPANSION
|
||||
setlocal ENABLEEXTENSIONS
|
||||
|
||||
set GIT_VERSION_FILE=%~p0..\rpcs3\git-version.h
|
||||
if not defined GIT (
|
||||
@@ -48,40 +47,14 @@ if errorlevel 1 (
|
||||
echo // This is a generated file. > "%GIT_VERSION_FILE%"
|
||||
echo. >> "%GIT_VERSION_FILE%"
|
||||
echo #define RPCS3_GIT_VERSION "unknown" >> "%GIT_VERSION_FILE%"
|
||||
echo #define RPCS3_GIT_BRANCH "unknown" >> "%GIT_VERSION_FILE%"
|
||||
echo. >> "%GIT_VERSION_FILE%"
|
||||
echo // If you don't want this file to update/recompile, change to 1. >> "%GIT_VERSION_FILE%"
|
||||
echo #define RPCS3_GIT_VERSION_NO_UPDATE 0 >> "%GIT_VERSION_FILE%"
|
||||
goto done
|
||||
)
|
||||
|
||||
rem // Get commit count from (unshallowed) HEAD
|
||||
for /F %%I IN ('call %GIT% rev-list HEAD --count') do set COMMIT_COUNT=%%I
|
||||
|
||||
rem // If we're in AppVeyor, building a non-master, pull request artifact
|
||||
if defined APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH (
|
||||
|
||||
if "%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%"=="master" (
|
||||
rem // If pull request comes from a master branch, GIT_BRANCH = username/branch in order to distinguish from upstream/master
|
||||
for /f "tokens=1* delims=/" %%a in ("%APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME%") do set user=%%a
|
||||
set "GIT_BRANCH=!user!/%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%"
|
||||
) else (
|
||||
rem // Otherwise, GIT_BRANCH=branch
|
||||
set GIT_BRANCH=%APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH%
|
||||
)
|
||||
|
||||
rem // Make GIT_VERSION the last commit (shortened); Don't include commit count on non-master builds
|
||||
for /F %%I IN ('call %GIT% rev-parse --short HEAD') do set GIT_VERSION=%%I
|
||||
|
||||
) else (
|
||||
rem // Get last commit (shortened) and concat after commit count in GIT_VERSION
|
||||
for /F %%I IN ('call %GIT% rev-parse --short HEAD') do set GIT_VERSION=%COMMIT_COUNT%-%%I
|
||||
|
||||
for /F %%I IN ('call %GIT% rev-parse --abbrev-ref HEAD') do set GIT_BRANCH=%%I
|
||||
)
|
||||
|
||||
rem // Echo obtained GIT_VERSION for debug purposes if needed
|
||||
echo %GIT_VERSION%
|
||||
for /F %%I IN ('call %GIT% rev-list HEAD --count') do set GIT_VERSION=%%I
|
||||
for /F %%I IN ('call %GIT% rev-parse --short HEAD') do set GIT_VERSION=%GIT_VERSION%-%%I
|
||||
|
||||
rem // Don't modify the file if it already has the current version.
|
||||
if exist "%GIT_VERSION_FILE%" (
|
||||
@@ -94,7 +67,6 @@ if exist "%GIT_VERSION_FILE%" (
|
||||
echo // This is a generated file. > "%GIT_VERSION_FILE%"
|
||||
echo. >> "%GIT_VERSION_FILE%"
|
||||
echo #define RPCS3_GIT_VERSION "%GIT_VERSION%" >> "%GIT_VERSION_FILE%"
|
||||
echo #define RPCS3_GIT_BRANCH ^"%GIT_BRANCH%^" >> "%GIT_VERSION_FILE%"
|
||||
echo. >> "%GIT_VERSION_FILE%"
|
||||
echo // If you don't want this file to update/recompile, change to 1. >> "%GIT_VERSION_FILE%"
|
||||
echo #define RPCS3_GIT_VERSION_NO_UPDATE 0 >> "%GIT_VERSION_FILE%"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace rpcs3
|
||||
{
|
||||
template<typename T>
|
||||
static size_t hash_base(T value)
|
||||
{
|
||||
return static_cast<size_t>(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static size_t hash_struct(const T& value)
|
||||
{
|
||||
// FNV 64-bit
|
||||
size_t result = 14695981039346656037ull;
|
||||
const unsigned char *bytes = reinterpret_cast<const unsigned char*>(&value);
|
||||
|
||||
for (size_t n = 0; n < sizeof(T); ++n)
|
||||
{
|
||||
result ^= bytes[n];
|
||||
result *= 1099511628211ull;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
|
||||
//! Simple sizeless array base for concurrent access. Cannot shrink, only growths automatically.
|
||||
//! There is no way to know the current size. The smaller index is, the faster it's accessed.
|
||||
//!
|
||||
//! T is the type of elements. Currently, default constructor of T shall be constexpr.
|
||||
//! N is initial element count, available without any memory allocation and only stored contiguously.
|
||||
template<typename T, std::size_t N>
|
||||
class lf_array
|
||||
{
|
||||
// Data (default-initialized)
|
||||
T m_data[N]{};
|
||||
|
||||
// Next array block
|
||||
atomic_t<lf_array*> m_next{};
|
||||
|
||||
public:
|
||||
constexpr lf_array() = default;
|
||||
|
||||
~lf_array()
|
||||
{
|
||||
for (auto ptr = m_next.raw(); UNLIKELY(ptr);)
|
||||
{
|
||||
delete std::exchange(ptr, std::exchange(ptr->m_next.raw(), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
T& operator [](std::size_t index)
|
||||
{
|
||||
if (LIKELY(index < N))
|
||||
{
|
||||
return m_data[index];
|
||||
}
|
||||
else if (UNLIKELY(!m_next))
|
||||
{
|
||||
// Create new array block. It's not a full-fledged once-synchronization, unlikely needed.
|
||||
for (auto _new = new lf_array, ptr = this; UNLIKELY(ptr);)
|
||||
{
|
||||
// Install the pointer. If failed, go deeper.
|
||||
ptr = ptr->m_next.compare_and_swap(nullptr, _new);
|
||||
}
|
||||
}
|
||||
|
||||
// Access recursively
|
||||
return (*m_next)[index - N];
|
||||
}
|
||||
};
|
||||
|
||||
//! Simple lock-free FIFO queue base. Based on lf_array<T, N> itself. Currently uses 32-bit counters.
|
||||
//! There is no "push_end" or "pop_begin" provided, the queue element must signal its state on its own.
|
||||
template<typename T, std::size_t N>
|
||||
class lf_fifo : public lf_array<T, N>
|
||||
{
|
||||
struct alignas(8) ctrl_t
|
||||
{
|
||||
u32 push;
|
||||
u32 pop;
|
||||
};
|
||||
|
||||
atomic_t<ctrl_t> m_ctrl{};
|
||||
|
||||
public:
|
||||
constexpr lf_fifo() = default;
|
||||
|
||||
// Get current "push" position
|
||||
u32 size() const
|
||||
{
|
||||
return reinterpret_cast<const atomic_t<u32>&>(m_ctrl).load(); // Hack
|
||||
}
|
||||
|
||||
// Acquire the place for one or more elements.
|
||||
u32 push_begin(u32 count = 1)
|
||||
{
|
||||
return reinterpret_cast<atomic_t<u32>&>(m_ctrl).fetch_add(count); // Hack
|
||||
}
|
||||
|
||||
// Get current "pop" position
|
||||
u32 peek() const
|
||||
{
|
||||
return m_ctrl.load().pop;
|
||||
}
|
||||
|
||||
// Acknowledge processed element, return number of the next one.
|
||||
// Perform clear if possible, zero is returned in this case.
|
||||
u32 pop_end(u32 count = 1)
|
||||
{
|
||||
return m_ctrl.atomic_op([&](ctrl_t& ctrl)
|
||||
{
|
||||
ctrl.pop += count;
|
||||
|
||||
if (ctrl.pop == ctrl.push)
|
||||
{
|
||||
// Clean if possible
|
||||
ctrl.push = 0;
|
||||
ctrl.pop = 0;
|
||||
}
|
||||
|
||||
return ctrl.pop;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//! Simple lock-free map. Based on lf_array<>. All elements are accessible, implicitly initialized.
|
||||
template<typename K, typename T, typename Hash = value_hash<K>, std::size_t Size = 256>
|
||||
class lf_hashmap
|
||||
{
|
||||
struct pair_t
|
||||
{
|
||||
// Default-constructed key means "no key"
|
||||
atomic_t<K> key{};
|
||||
T value{};
|
||||
};
|
||||
|
||||
//
|
||||
lf_array<pair_t, Size> m_data{};
|
||||
|
||||
// Value for default-constructed key
|
||||
T m_default_key_data{};
|
||||
|
||||
public:
|
||||
constexpr lf_hashmap() = default;
|
||||
|
||||
// Access element (added implicitly)
|
||||
T& operator [](const K& key)
|
||||
{
|
||||
if (UNLIKELY(key == K{}))
|
||||
{
|
||||
return m_default_key_data;
|
||||
}
|
||||
|
||||
// Calculate hash and array position
|
||||
for (std::size_t pos = Hash{}(key) % Size;; pos += Size)
|
||||
{
|
||||
// Access the array
|
||||
auto& pair = m_data[pos];
|
||||
|
||||
// Check the key value (optimistic)
|
||||
if (LIKELY(pair.key == key) || pair.key.compare_and_swap_test(K{}, key))
|
||||
{
|
||||
return pair.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Fixed-size single-producer single-consumer queue
|
||||
template <typename T, std::uint32_t N>
|
||||
class lf_spsc
|
||||
{
|
||||
// If N is a power of 2, m_push/m_pop can safely overflow and the algorithm is simplified
|
||||
static_assert(N && (1u << 31) % N == 0, "lf_spsc<> error: size must be power of 2");
|
||||
|
||||
protected:
|
||||
volatile std::uint32_t m_push{0};
|
||||
volatile std::uint32_t m_pop{0};
|
||||
T m_data[N]{};
|
||||
|
||||
public:
|
||||
constexpr lf_spsc() = default;
|
||||
|
||||
// Try to push (producer only)
|
||||
template <typename T2>
|
||||
bool try_push(T2&& data)
|
||||
{
|
||||
const std::uint32_t pos = m_push;
|
||||
|
||||
if (pos - m_pop >= N)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_mm_lfence();
|
||||
m_data[pos % N] = std::forward<T2>(data);
|
||||
_mm_sfence();
|
||||
m_push = pos + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to get push pointer (producer only)
|
||||
operator T*()
|
||||
{
|
||||
const std::uint32_t pos = m_push;
|
||||
|
||||
if (pos - m_pop >= N)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_mm_lfence();
|
||||
return m_data + (pos % N);
|
||||
}
|
||||
|
||||
// Increment push counter (producer only)
|
||||
void end_push()
|
||||
{
|
||||
const std::uint32_t pos = m_push;
|
||||
|
||||
if (pos - m_pop < N)
|
||||
{
|
||||
_mm_sfence();
|
||||
m_push = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe access
|
||||
T& get_push(std::size_t i)
|
||||
{
|
||||
_mm_lfence();
|
||||
return m_data[(m_push + i) % N];
|
||||
}
|
||||
|
||||
// Try to pop (consumer only)
|
||||
template <typename T2>
|
||||
bool try_pop(T2& out)
|
||||
{
|
||||
const std::uint32_t pos = m_pop;
|
||||
|
||||
if (m_push - pos <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_mm_lfence();
|
||||
out = std::move(m_data[pos % N]);
|
||||
_mm_sfence();
|
||||
m_pop = pos + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Increment pop counter (consumer only)
|
||||
void end_pop()
|
||||
{
|
||||
const std::uint32_t pos = m_pop;
|
||||
|
||||
if (m_push - pos > 0)
|
||||
{
|
||||
_mm_sfence();
|
||||
m_pop = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get size (consumer only)
|
||||
std::uint32_t size() const
|
||||
{
|
||||
return m_push - m_pop;
|
||||
}
|
||||
|
||||
// Direct access (consumer only)
|
||||
T& operator [](std::size_t i)
|
||||
{
|
||||
_mm_lfence();
|
||||
return m_data[(m_pop + i) % N];
|
||||
}
|
||||
};
|
||||
|
||||
// Fixed-size multi-producer single-consumer queue
|
||||
template <typename T, std::uint32_t N>
|
||||
class lf_mpsc : lf_spsc<T, N>
|
||||
{
|
||||
protected:
|
||||
using lf_spsc<T, N>::m_push;
|
||||
using lf_spsc<T, N>::m_pop;
|
||||
using lf_spsc<T, N>::m_data;
|
||||
|
||||
enum : std::uint64_t
|
||||
{
|
||||
c_ack = 1ull << 0,
|
||||
c_rel = 1ull << 32,
|
||||
};
|
||||
|
||||
atomic_t<std::uint64_t> m_lock{0};
|
||||
|
||||
void release(std::uint64_t value)
|
||||
{
|
||||
// Push all pending elements at once when possible
|
||||
if (value && value % c_rel == value / c_rel)
|
||||
{
|
||||
_mm_sfence();
|
||||
m_push += value % c_rel;
|
||||
m_lock.compare_and_swap_test(value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr lf_mpsc() = default;
|
||||
|
||||
// Try to get push pointer
|
||||
operator T*()
|
||||
{
|
||||
const std::uint64_t old = m_lock.fetch_add(c_ack);
|
||||
const std::uint32_t pos = m_push;
|
||||
|
||||
if (old % N >= N || pos - m_pop >= N - (old % N))
|
||||
{
|
||||
release(m_lock.sub_fetch(c_ack));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_data + ((pos + old) % N);
|
||||
}
|
||||
|
||||
// Increment push counter (producer only)
|
||||
void end_push()
|
||||
{
|
||||
release(m_lock.add_fetch(c_rel));
|
||||
}
|
||||
|
||||
// Try to push
|
||||
template <typename T2>
|
||||
bool try_push(T2&& data)
|
||||
{
|
||||
if (T* ptr = *this)
|
||||
{
|
||||
*ptr = std::forward<T2>(data);
|
||||
end_push();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable consumer methods
|
||||
using lf_spsc<T, N>::try_pop;
|
||||
using lf_spsc<T, N>::end_pop;
|
||||
using lf_spsc<T, N>::size;
|
||||
using lf_spsc<T, N>::operator [];
|
||||
};
|
||||
@@ -1,334 +0,0 @@
|
||||
#include "mutex.h"
|
||||
#include "sync.h"
|
||||
|
||||
#include <climits>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
// TLS variable for tracking owned mutexes
|
||||
thread_local std::vector<shared_mutex*> g_tls_locks;
|
||||
|
||||
void shared_mutex::imp_lock_shared(s64 _old)
|
||||
{
|
||||
verify("shared_mutex overflow" HERE), _old <= c_max;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
busy_wait();
|
||||
|
||||
const s64 value = m_value.load();
|
||||
|
||||
if (value >= c_min && m_value.compare_and_swap_test(value, value - c_min))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Acquire writer lock
|
||||
imp_wait(m_value.load());
|
||||
|
||||
// Convert to reader lock
|
||||
s64 value = m_value.fetch_add(c_one - c_min);
|
||||
|
||||
// Proceed exclusively
|
||||
return;
|
||||
|
||||
if (value != 0)
|
||||
{
|
||||
imp_unlock(value);
|
||||
}
|
||||
|
||||
// Wait as a reader if necessary
|
||||
if (value + c_one - c_min < 0)
|
||||
{
|
||||
NtWaitForKeyedEvent(nullptr, (int*)&m_value + 1, false, nullptr);
|
||||
}
|
||||
#else
|
||||
// Acquire writer lock
|
||||
imp_wait(0);
|
||||
|
||||
// Convert to reader lock
|
||||
m_value += c_one - c_min;
|
||||
|
||||
// Disabled code
|
||||
while (false)
|
||||
{
|
||||
const s64 value0 = m_value.fetch_op([](s64& value)
|
||||
{
|
||||
if (value >= c_min)
|
||||
{
|
||||
value -= c_min;
|
||||
}
|
||||
});
|
||||
|
||||
if (value0 >= c_min)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Acquire writer lock
|
||||
imp_wait(value0);
|
||||
|
||||
// Convert to reader lock
|
||||
s64 value1 = m_value.fetch_add(c_one - c_min);
|
||||
|
||||
if (value1 != 0)
|
||||
{
|
||||
imp_unlock(value1);
|
||||
}
|
||||
|
||||
value1 += c_one - c_min;
|
||||
|
||||
if (value1 > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait as a reader if necessary
|
||||
while (futex((int*)&m_value.raw() + IS_LE_MACHINE, FUTEX_WAIT_BITSET_PRIVATE, int(value1 >> 32), nullptr, nullptr, INT_MIN))
|
||||
{
|
||||
value1 = m_value.load();
|
||||
|
||||
if (value1 >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If blocked by writers, release the reader lock and try again
|
||||
const s64 value2 = m_value.fetch_op([](s64& value)
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
value += c_min;
|
||||
}
|
||||
});
|
||||
|
||||
if (value2 >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
imp_unlock_shared(value2);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void shared_mutex::imp_unlock_shared(s64 _old)
|
||||
{
|
||||
verify("shared_mutex overflow" HERE), _old + c_min <= c_max;
|
||||
|
||||
// Check reader count, notify the writer if necessary
|
||||
if ((_old + c_min) % c_one == 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
NtReleaseKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
#else
|
||||
m_value -= c_sig;
|
||||
|
||||
futex((int*)&m_value.raw() + IS_LE_MACHINE, FUTEX_WAKE_BITSET_PRIVATE, 1, nullptr, nullptr, u32(c_sig >> 32));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void shared_mutex::imp_wait(s64)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_value.sub_fetch(c_one))
|
||||
{
|
||||
NtWaitForKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
}
|
||||
#else
|
||||
if (!m_value.sub_fetch(c_one))
|
||||
{
|
||||
// Return immediately if locked
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Load new value, try to acquire c_sig
|
||||
const s64 value = m_value.fetch_op([](s64& value)
|
||||
{
|
||||
if (value <= c_one - c_sig)
|
||||
{
|
||||
value += c_sig;
|
||||
}
|
||||
});
|
||||
|
||||
if (value <= c_one - c_sig)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
futex((int*)&m_value.raw() + IS_LE_MACHINE, FUTEX_WAIT_BITSET_PRIVATE, int(value >> 32), nullptr, nullptr, u32(c_sig >> 32));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void shared_mutex::imp_lock(s64 _old)
|
||||
{
|
||||
verify("shared_mutex overflow" HERE), _old <= c_max;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
busy_wait();
|
||||
|
||||
const s64 value = m_value.load();
|
||||
|
||||
if (value == c_one && m_value.compare_and_swap_test(c_one, 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
imp_wait(m_value.load());
|
||||
}
|
||||
|
||||
void shared_mutex::imp_unlock(s64 _old)
|
||||
{
|
||||
verify("shared_mutex overflow" HERE), _old + c_one <= c_max;
|
||||
|
||||
// 1) Notify the next writer if necessary
|
||||
// 2) Notify all readers otherwise if necessary
|
||||
#ifdef _WIN32
|
||||
if (_old + c_one <= 0)
|
||||
{
|
||||
NtReleaseKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
}
|
||||
else if (s64 count = -_old / c_min * 0)
|
||||
{
|
||||
// Disabled code
|
||||
while (count--)
|
||||
{
|
||||
NtReleaseKeyedEvent(nullptr, (int*)&m_value + 1, false, nullptr);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (_old + c_one <= 0)
|
||||
{
|
||||
m_value -= c_sig;
|
||||
|
||||
futex((int*)&m_value.raw() + IS_LE_MACHINE, FUTEX_WAKE_BITSET_PRIVATE, 1, nullptr, nullptr, u32(c_sig >> 32));
|
||||
}
|
||||
else if (false)
|
||||
{
|
||||
// Disabled code
|
||||
futex((int*)&m_value.raw() + IS_LE_MACHINE, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, nullptr, INT_MIN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void shared_mutex::imp_lock_upgrade()
|
||||
{
|
||||
// TODO
|
||||
unlock_shared();
|
||||
lock();
|
||||
}
|
||||
|
||||
void shared_mutex::imp_lock_degrade()
|
||||
{
|
||||
// TODO
|
||||
unlock();
|
||||
lock_shared();
|
||||
}
|
||||
|
||||
bool shared_mutex::try_lock_shared()
|
||||
{
|
||||
// Conditional decrement
|
||||
return m_value.fetch_op([](s64& value) { if (value >= c_min) value -= c_min; }) >= c_min;
|
||||
}
|
||||
|
||||
bool shared_mutex::try_lock()
|
||||
{
|
||||
// Conditional decrement (TODO: obtain c_sig)
|
||||
return m_value.compare_and_swap_test(c_one, 0);
|
||||
}
|
||||
|
||||
bool shared_mutex::try_lock_upgrade()
|
||||
{
|
||||
// TODO
|
||||
return m_value.compare_and_swap_test(c_one - c_min, 0);
|
||||
}
|
||||
|
||||
bool shared_mutex::try_lock_degrade()
|
||||
{
|
||||
// TODO
|
||||
return m_value.compare_and_swap_test(0, c_one - c_min);
|
||||
}
|
||||
|
||||
safe_reader_lock::safe_reader_lock(shared_mutex& mutex)
|
||||
: m_mutex(mutex)
|
||||
, m_is_owned(false)
|
||||
{
|
||||
if (std::count(g_tls_locks.cbegin(), g_tls_locks.cend(), &m_mutex) == 0)
|
||||
{
|
||||
m_is_owned = true;
|
||||
|
||||
if (m_is_owned)
|
||||
{
|
||||
m_mutex.lock_shared();
|
||||
g_tls_locks.emplace_back(&m_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: order locks
|
||||
}
|
||||
}
|
||||
|
||||
safe_reader_lock::~safe_reader_lock()
|
||||
{
|
||||
if (m_is_owned)
|
||||
{
|
||||
m_mutex.unlock_shared();
|
||||
g_tls_locks.erase(std::remove(g_tls_locks.begin(), g_tls_locks.end(), &m_mutex), g_tls_locks.cend());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: order locks
|
||||
}
|
||||
|
||||
safe_writer_lock::safe_writer_lock(shared_mutex& mutex)
|
||||
: m_mutex(mutex)
|
||||
, m_is_owned(false)
|
||||
, m_is_upgraded(false)
|
||||
{
|
||||
if (std::count(g_tls_locks.cbegin(), g_tls_locks.cend(), &m_mutex) == 0)
|
||||
{
|
||||
m_is_owned = true;
|
||||
|
||||
if (m_is_owned)
|
||||
{
|
||||
m_mutex.lock_shared();
|
||||
g_tls_locks.emplace_back(&m_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: order locks
|
||||
}
|
||||
|
||||
if (m_mutex.is_reading())
|
||||
{
|
||||
m_is_upgraded = true;
|
||||
m_mutex.lock_upgrade();
|
||||
}
|
||||
}
|
||||
|
||||
safe_writer_lock::~safe_writer_lock()
|
||||
{
|
||||
if (m_is_upgraded)
|
||||
{
|
||||
m_mutex.lock_degrade();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_is_owned)
|
||||
{
|
||||
m_mutex.unlock();
|
||||
g_tls_locks.erase(std::remove(g_tls_locks.begin(), g_tls_locks.end(), &m_mutex), g_tls_locks.cend());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: order locks
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
|
||||
// Shared mutex.
|
||||
class shared_mutex final
|
||||
{
|
||||
enum : s64
|
||||
{
|
||||
c_one = 1ull << 31, // Fixed-point 1.0 value (one writer)
|
||||
c_min = 0x00000001, // Fixed-point 1.0/max_readers value
|
||||
c_sig = 1ull << 62,
|
||||
c_max = c_one
|
||||
};
|
||||
|
||||
atomic_t<s64> m_value{c_one}; // Semaphore-alike counter
|
||||
|
||||
void imp_lock_shared(s64 _old);
|
||||
void imp_unlock_shared(s64 _old);
|
||||
void imp_wait(s64 _old);
|
||||
void imp_lock(s64 _old);
|
||||
void imp_unlock(s64 _old);
|
||||
|
||||
void imp_lock_upgrade();
|
||||
void imp_lock_degrade();
|
||||
|
||||
public:
|
||||
constexpr shared_mutex() = default;
|
||||
|
||||
bool try_lock_shared();
|
||||
|
||||
void lock_shared()
|
||||
{
|
||||
const s64 value = m_value.load();
|
||||
|
||||
// Fast path: decrement if positive
|
||||
if (UNLIKELY(value < c_min || value > c_one || !m_value.compare_and_swap_test(value, value - c_min)))
|
||||
{
|
||||
imp_lock_shared(value);
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_shared()
|
||||
{
|
||||
// Unconditional increment
|
||||
const s64 value = m_value.fetch_add(c_min);
|
||||
|
||||
if (value < 0 || value > c_one - c_min)
|
||||
{
|
||||
imp_unlock_shared(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock();
|
||||
|
||||
void lock()
|
||||
{
|
||||
// Try to lock
|
||||
const s64 value = m_value.compare_and_swap(c_one, 0);
|
||||
|
||||
if (value != c_one)
|
||||
{
|
||||
imp_lock(value);
|
||||
}
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
// Unconditional increment
|
||||
const s64 value = m_value.fetch_add(c_one);
|
||||
|
||||
if (value != 0)
|
||||
{
|
||||
imp_unlock(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock_upgrade();
|
||||
|
||||
void lock_upgrade()
|
||||
{
|
||||
if (!m_value.compare_and_swap_test(c_one - c_min, 0))
|
||||
{
|
||||
imp_lock_upgrade();
|
||||
}
|
||||
}
|
||||
|
||||
bool try_lock_degrade();
|
||||
|
||||
void lock_degrade()
|
||||
{
|
||||
if (!m_value.compare_and_swap_test(0, c_one - c_min))
|
||||
{
|
||||
imp_lock_degrade();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_reading() const
|
||||
{
|
||||
return (m_value.load() % c_one) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Simplified shared (reader) lock implementation.
|
||||
class reader_lock final
|
||||
{
|
||||
shared_mutex& m_mutex;
|
||||
bool m_upgraded = false;
|
||||
|
||||
void lock()
|
||||
{
|
||||
m_upgraded ? m_mutex.lock() : m_mutex.lock_shared();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
m_upgraded ? m_mutex.unlock() : m_mutex.unlock_shared();
|
||||
}
|
||||
|
||||
friend class cond_variable;
|
||||
|
||||
public:
|
||||
reader_lock(const reader_lock&) = delete;
|
||||
|
||||
explicit reader_lock(shared_mutex& mutex)
|
||||
: m_mutex(mutex)
|
||||
{
|
||||
lock();
|
||||
}
|
||||
|
||||
// One-way lock upgrade
|
||||
void upgrade()
|
||||
{
|
||||
if (!m_upgraded)
|
||||
{
|
||||
m_mutex.lock_upgrade();
|
||||
m_upgraded = true;
|
||||
}
|
||||
}
|
||||
|
||||
~reader_lock()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
|
||||
// Simplified exclusive (writer) lock implementation.
|
||||
class writer_lock final
|
||||
{
|
||||
shared_mutex& m_mutex;
|
||||
|
||||
void lock()
|
||||
{
|
||||
m_mutex.lock();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
friend class cond_variable;
|
||||
|
||||
public:
|
||||
writer_lock(const writer_lock&) = delete;
|
||||
|
||||
explicit writer_lock(shared_mutex& mutex)
|
||||
: m_mutex(mutex)
|
||||
{
|
||||
lock();
|
||||
}
|
||||
|
||||
~writer_lock()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
|
||||
// Safe reader lock. Can be recursive above other safe locks (reader or writer).
|
||||
class safe_reader_lock final
|
||||
{
|
||||
shared_mutex& m_mutex;
|
||||
bool m_is_owned;
|
||||
|
||||
void lock()
|
||||
{
|
||||
m_mutex.lock_shared();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
m_mutex.unlock_shared();
|
||||
}
|
||||
|
||||
friend class cond_variable;
|
||||
|
||||
public:
|
||||
safe_reader_lock(const safe_reader_lock&) = delete;
|
||||
|
||||
explicit safe_reader_lock(shared_mutex& mutex);
|
||||
|
||||
~safe_reader_lock();
|
||||
};
|
||||
|
||||
// Safe writer lock. Can be recursive above other safe locks. Performs upgrade and degrade operations above existing reader lock if necessary.
|
||||
class safe_writer_lock final
|
||||
{
|
||||
shared_mutex& m_mutex;
|
||||
bool m_is_owned;
|
||||
bool m_is_upgraded;
|
||||
|
||||
void lock()
|
||||
{
|
||||
m_mutex.lock();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
friend class cond_variable;
|
||||
|
||||
public:
|
||||
safe_writer_lock(const safe_writer_lock&) = delete;
|
||||
|
||||
explicit safe_writer_lock(shared_mutex& mutex);
|
||||
|
||||
~safe_writer_lock();
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "stdafx.h"
|
||||
#include "restore_new.h"
|
||||
#include "Utilities/Log.h"
|
||||
#pragma warning(push)
|
||||
#pragma message("TODO: remove wx dependency: <wx/image.h>")
|
||||
#pragma warning(disable : 4996)
|
||||
#include <wx/image.h>
|
||||
#pragma warning(pop)
|
||||
#include "define_new_memleakdetect.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#include "rPlatform.h"
|
||||
|
||||
rImage::rImage()
|
||||
{
|
||||
handle = static_cast<void*>(new wxImage());
|
||||
}
|
||||
|
||||
rImage::~rImage()
|
||||
{
|
||||
delete static_cast<wxImage*>(handle);
|
||||
}
|
||||
|
||||
void rImage::Create(int width, int height, void *data, void *alpha)
|
||||
{
|
||||
static_cast<wxImage*>(handle)->Create(width, height, static_cast<unsigned char*>(data), static_cast<unsigned char*>(alpha));
|
||||
}
|
||||
void rImage::SaveFile(const std::string& name, rImageType type)
|
||||
{
|
||||
if (type == rBITMAP_TYPE_PNG)
|
||||
{
|
||||
static_cast<wxImage*>(handle)->SaveFile(fmt::FromUTF8(name),wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw EXCEPTION("unsupported type");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
/**********************************************************************
|
||||
*********** RSX Debugger
|
||||
************************************************************************/
|
||||
|
||||
struct RSXDebuggerProgram
|
||||
{
|
||||
u32 id;
|
||||
u32 vp_id;
|
||||
u32 fp_id;
|
||||
std::string vp_shader;
|
||||
std::string fp_shader;
|
||||
bool modified;
|
||||
|
||||
RSXDebuggerProgram()
|
||||
: modified(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
extern std::vector<RSXDebuggerProgram> m_debug_programs;
|
||||
|
||||
/**********************************************************************
|
||||
*********** Image stuff
|
||||
************************************************************************/
|
||||
enum rImageType
|
||||
{
|
||||
rBITMAP_TYPE_PNG
|
||||
};
|
||||
struct rImage
|
||||
{
|
||||
rImage();
|
||||
rImage(const rImage &) = delete;
|
||||
~rImage();
|
||||
void Create(int width , int height, void *data, void *alpha);
|
||||
void SaveFile(const std::string& name, rImageType type);
|
||||
|
||||
void *handle;
|
||||
};
|
||||
@@ -0,0 +1,235 @@
|
||||
#include "stdafx.h"
|
||||
#include "rTime.h"
|
||||
#pragma warning(push)
|
||||
#pragma message("TODO: remove wx dependency: <wx/datetime.h>")
|
||||
#pragma warning(disable : 4996)
|
||||
#include <wx/datetime.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
std::string rDefaultDateTimeFormat = "%c";
|
||||
|
||||
rTimeSpan::rTimeSpan()
|
||||
{
|
||||
handle = static_cast<void *>(new wxTimeSpan());
|
||||
}
|
||||
|
||||
rTimeSpan::~rTimeSpan()
|
||||
{
|
||||
delete static_cast<wxTimeSpan*>(handle);
|
||||
}
|
||||
|
||||
rTimeSpan::rTimeSpan(const rTimeSpan& other)
|
||||
|
||||
{
|
||||
handle = static_cast<void *>(new wxTimeSpan(*static_cast<wxTimeSpan*>(other.handle)));
|
||||
|
||||
}
|
||||
|
||||
rTimeSpan::rTimeSpan(int a, int b , int c, int d)
|
||||
{
|
||||
handle = static_cast<void *>(new wxTimeSpan(a,b,c,d));
|
||||
}
|
||||
|
||||
|
||||
rDateSpan::rDateSpan()
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateSpan());
|
||||
}
|
||||
|
||||
rDateSpan::~rDateSpan()
|
||||
{
|
||||
delete static_cast<wxDateSpan*>(handle);
|
||||
}
|
||||
|
||||
rDateSpan::rDateSpan(const rDateSpan& other)
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateSpan(*static_cast<wxDateSpan*>(other.handle)));
|
||||
}
|
||||
|
||||
rDateSpan::rDateSpan(int a, int b, int c, int d)
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateSpan(a,b,c,d));
|
||||
}
|
||||
|
||||
rDateTime::rDateTime()
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateTime());
|
||||
}
|
||||
|
||||
rDateTime::~rDateTime()
|
||||
{
|
||||
delete static_cast<wxDateTime*>(handle);
|
||||
}
|
||||
|
||||
rDateTime::rDateTime(const rDateTime& other)
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateTime(*static_cast<wxDateTime*>(other.handle)));
|
||||
}
|
||||
|
||||
rDateTime::rDateTime(const time_t& time)
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateTime(time));
|
||||
}
|
||||
|
||||
rDateTime::rDateTime(u16 day, rDateTime::Month month, u16 year, u16 hour, u16 minute, u16 second, u32 millisecond)
|
||||
{
|
||||
handle = static_cast<void *>(new wxDateTime(day,(wxDateTime::Month)month,year,hour,minute,second,millisecond));
|
||||
}
|
||||
|
||||
rDateTime rDateTime::UNow()
|
||||
{
|
||||
rDateTime time;
|
||||
delete static_cast<wxDateTime*>(time.handle);
|
||||
time.handle = static_cast<void *>(new wxDateTime(wxDateTime::UNow()));
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
rDateTime rDateTime::FromUTC(bool val)
|
||||
{
|
||||
rDateTime time(*this);
|
||||
void *temp = time.handle;
|
||||
|
||||
time.handle = static_cast<void *>(new wxDateTime(static_cast<wxDateTime*>(temp)->FromTimezone(wxDateTime::GMT0, val)));
|
||||
delete static_cast<wxDateTime*>(temp);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
rDateTime rDateTime::ToUTC(bool val)
|
||||
{
|
||||
rDateTime time(*this);
|
||||
void *temp = time.handle;
|
||||
|
||||
time.handle = static_cast<void *>(new wxDateTime(static_cast<wxDateTime*>(temp)->ToTimezone(wxDateTime::GMT0, val)));
|
||||
delete static_cast<wxDateTime*>(temp);
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
time_t rDateTime::GetTicks()
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetTicks();
|
||||
}
|
||||
|
||||
void rDateTime::Add(const rTimeSpan& span)
|
||||
{
|
||||
static_cast<wxDateTime*>(handle)->Add(*static_cast<wxTimeSpan*>(span.handle));
|
||||
}
|
||||
|
||||
void rDateTime::Add(const rDateSpan& span)
|
||||
{
|
||||
static_cast<wxDateTime*>(handle)->Add(*static_cast<wxDateSpan*>(span.handle));
|
||||
}
|
||||
|
||||
wxDateTime::TimeZone convertTZ(rDateTime::rTimeZone tz)
|
||||
{
|
||||
switch (tz)
|
||||
{
|
||||
case rDateTime::Local:
|
||||
return wxDateTime::Local;
|
||||
case rDateTime::GMT0:
|
||||
return wxDateTime::GMT0;
|
||||
case rDateTime::UTC:
|
||||
return wxDateTime::UTC;
|
||||
default:
|
||||
throw EXCEPTION("WRONG DATETIME");
|
||||
}
|
||||
}
|
||||
|
||||
std::string rDateTime::Format(const std::string &format, const rTimeZone &tz) const
|
||||
{
|
||||
return fmt::ToUTF8(static_cast<wxDateTime*>(handle)->Format(fmt::FromUTF8(format),convertTZ(tz)));
|
||||
}
|
||||
|
||||
void rDateTime::ParseDateTime(const char* format)
|
||||
{
|
||||
static_cast<wxDateTime*>(handle)->ParseDateTime(format);
|
||||
}
|
||||
|
||||
u32 rDateTime::GetAsDOS()
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetAsDOS();
|
||||
}
|
||||
|
||||
rDateTime &rDateTime::SetFromDOS(u32 fromdos)
|
||||
{
|
||||
static_cast<wxDateTime*>(handle)->SetFromDOS(fromdos);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool rDateTime::IsLeapYear(int year, rDateTime::Calender cal)
|
||||
{
|
||||
if (cal == Gregorian)
|
||||
{
|
||||
return wxDateTime::IsLeapYear(year, wxDateTime::Gregorian);
|
||||
}
|
||||
else
|
||||
{
|
||||
return wxDateTime::IsLeapYear(year, wxDateTime::Julian);
|
||||
}
|
||||
}
|
||||
|
||||
int rDateTime::GetNumberOfDays(rDateTime::Month month, int year, rDateTime::Calender cal)
|
||||
{
|
||||
if (cal == Gregorian)
|
||||
{
|
||||
return wxDateTime::GetNumberOfDays(static_cast<wxDateTime::Month>(month), year, wxDateTime::Gregorian);
|
||||
}
|
||||
else
|
||||
{
|
||||
return wxDateTime::GetNumberOfDays(static_cast<wxDateTime::Month>(month), year, wxDateTime::Julian);
|
||||
}
|
||||
}
|
||||
|
||||
void rDateTime::SetToWeekDay(rDateTime::WeekDay day, int n, rDateTime::Month month, int year)
|
||||
{
|
||||
static_cast<wxDateTime*>(handle)->SetToWeekDay(
|
||||
static_cast<wxDateTime::WeekDay>(day)
|
||||
, n
|
||||
, static_cast<wxDateTime::Month>(month)
|
||||
, year
|
||||
);
|
||||
}
|
||||
|
||||
int rDateTime::GetWeekDay()
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetWeekDay();
|
||||
}
|
||||
|
||||
u16 rDateTime::GetYear(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetYear(convertTZ(timezone));
|
||||
}
|
||||
|
||||
u16 rDateTime::GetMonth(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetMonth(convertTZ(timezone));
|
||||
}
|
||||
|
||||
u16 rDateTime::GetDay(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetDay(convertTZ(timezone));
|
||||
}
|
||||
|
||||
u16 rDateTime::GetHour(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetHour(convertTZ(timezone));
|
||||
}
|
||||
|
||||
u16 rDateTime::GetMinute(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetMinute(convertTZ(timezone));
|
||||
}
|
||||
|
||||
u16 rDateTime::GetSecond(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetSecond(convertTZ(timezone));
|
||||
}
|
||||
|
||||
u32 rDateTime::GetMillisecond(rDateTime::TZ timezone)
|
||||
{
|
||||
return static_cast<wxDateTime*>(handle)->GetMillisecond(convertTZ(timezone));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
extern std::string rDefaultDateTimeFormat;
|
||||
|
||||
|
||||
struct rTimeSpan
|
||||
{
|
||||
rTimeSpan();
|
||||
~rTimeSpan();
|
||||
rTimeSpan(const rTimeSpan& other);
|
||||
rTimeSpan(int, int, int, int);
|
||||
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct rDateSpan
|
||||
{
|
||||
rDateSpan();
|
||||
~rDateSpan();
|
||||
rDateSpan(const rDateSpan& other);
|
||||
rDateSpan(int, int, int, int);
|
||||
|
||||
void *handle;
|
||||
};
|
||||
|
||||
struct rDateTime
|
||||
{
|
||||
enum TZ
|
||||
{
|
||||
Local, GMT0,UTC
|
||||
};
|
||||
enum Calender
|
||||
{
|
||||
Gregorian, Julian
|
||||
};
|
||||
|
||||
using rTimeZone = TZ;
|
||||
|
||||
enum WeekDay
|
||||
{
|
||||
Sun = 0,
|
||||
Mon,
|
||||
Tue,
|
||||
Wed,
|
||||
Thu,
|
||||
Fri,
|
||||
Sat,
|
||||
Inv_WeekDay
|
||||
};
|
||||
|
||||
enum Month {
|
||||
Jan = 0,
|
||||
Feb = 1,
|
||||
Mar = 2,
|
||||
Apr = 3,
|
||||
May = 4,
|
||||
Jun = 5,
|
||||
Jul = 6,
|
||||
Aug = 7,
|
||||
Sep = 8,
|
||||
Oct = 9,
|
||||
Nov = 10,
|
||||
Dec = 11,
|
||||
Inv_Month = 12
|
||||
};
|
||||
|
||||
rDateTime();
|
||||
~rDateTime();
|
||||
rDateTime(const rDateTime& other);
|
||||
rDateTime(const time_t &time);
|
||||
rDateTime(u16 day, rDateTime::Month month, u16 year, u16 hour, u16 minute, u16 second, u32 millisecond);
|
||||
|
||||
static rDateTime UNow();
|
||||
rDateTime FromUTC(bool val);
|
||||
rDateTime ToUTC(bool val);
|
||||
time_t GetTicks();
|
||||
void Add(const rTimeSpan& span);
|
||||
void Add(const rDateSpan& span);
|
||||
void Close();
|
||||
std::string Format(const std::string &format = rDefaultDateTimeFormat, const rTimeZone &tz = Local) const;
|
||||
|
||||
void ParseDateTime(const char* format);
|
||||
|
||||
u32 GetAsDOS();
|
||||
rDateTime &SetFromDOS(u32 fromdos);
|
||||
|
||||
static bool IsLeapYear(int year, rDateTime::Calender cal);
|
||||
static int GetNumberOfDays(rDateTime::Month month, int year, rDateTime::Calender cal);
|
||||
void SetToWeekDay(rDateTime::WeekDay day, int n, rDateTime::Month month, int year);
|
||||
int GetWeekDay();
|
||||
|
||||
u16 GetYear( rDateTime::TZ timezone);
|
||||
u16 GetMonth(rDateTime::TZ timezone);
|
||||
u16 GetDay(rDateTime::TZ timezone);
|
||||
u16 GetHour(rDateTime::TZ timezone);
|
||||
u16 GetMinute(rDateTime::TZ timezone);
|
||||
u16 GetSecond(rDateTime::TZ timezone);
|
||||
u32 GetMillisecond(rDateTime::TZ timezone);
|
||||
|
||||
void *handle;
|
||||
};
|
||||
|
||||
+68
-24
@@ -1,73 +1,117 @@
|
||||
#include "stdafx.h"
|
||||
#include "restore_new.h"
|
||||
#include "Utilities/rXml.h"
|
||||
#include "define_new_memleakdetect.h"
|
||||
#pragma warning(push)
|
||||
#pragma message("TODO: remove wx dependency: <wx/xml/xml.h>")
|
||||
#pragma warning(disable : 4996)
|
||||
#include <wx/xml/xml.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
rXmlNode::rXmlNode() : handle()
|
||||
rXmlNode::rXmlNode()
|
||||
{
|
||||
ownPtr = true;
|
||||
handle = reinterpret_cast<void *>(new wxXmlNode());
|
||||
}
|
||||
|
||||
rXmlNode::rXmlNode(const pugi::xml_node &node)
|
||||
rXmlNode::rXmlNode(void *ptr)
|
||||
{
|
||||
handle = node;
|
||||
ownPtr = false;
|
||||
handle = ptr;
|
||||
}
|
||||
|
||||
rXmlNode::rXmlNode(const rXmlNode& other)
|
||||
{
|
||||
ownPtr = true;
|
||||
handle = reinterpret_cast<void *>(new wxXmlNode(*reinterpret_cast<wxXmlNode*>(other.handle)));
|
||||
}
|
||||
|
||||
rXmlNode &rXmlNode::operator=(const rXmlNode& other)
|
||||
{
|
||||
if (ownPtr)
|
||||
{
|
||||
delete reinterpret_cast<wxXmlNode*>(handle);
|
||||
}
|
||||
handle = reinterpret_cast<void *>(new wxXmlNode(*reinterpret_cast<wxXmlNode*>(other.handle)));
|
||||
ownPtr = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
rXmlNode::~rXmlNode()
|
||||
{
|
||||
if (ownPtr)
|
||||
{
|
||||
delete reinterpret_cast<wxXmlNode*>(handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<rXmlNode> rXmlNode::GetChildren()
|
||||
{
|
||||
// it.begin() returns node_iterator*, *it.begin() return node*.
|
||||
pugi::xml_object_range<pugi::xml_node_iterator> it = handle.children();
|
||||
pugi::xml_node begin = *it.begin();
|
||||
|
||||
if (begin)
|
||||
wxXmlNode* result = reinterpret_cast<wxXmlNode*>(handle)->GetChildren();
|
||||
if (result)
|
||||
{
|
||||
return std::make_shared<rXmlNode>(begin);
|
||||
return std::make_shared<rXmlNode>(reinterpret_cast<void*>(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
return std::shared_ptr<rXmlNode>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<rXmlNode> rXmlNode::GetNext()
|
||||
{
|
||||
pugi::xml_node result = handle.next_sibling();
|
||||
wxXmlNode* result = reinterpret_cast<wxXmlNode*>(handle)->GetNext();
|
||||
if (result)
|
||||
{
|
||||
return std::make_shared<rXmlNode>(result);
|
||||
return std::make_shared<rXmlNode>(reinterpret_cast<void*>(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
return std::shared_ptr<rXmlNode>(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string rXmlNode::GetName()
|
||||
{
|
||||
return handle.name();
|
||||
return fmt::ToUTF8(reinterpret_cast<wxXmlNode*>(handle)->GetName());
|
||||
}
|
||||
|
||||
std::string rXmlNode::GetAttribute(const std::string &name)
|
||||
{
|
||||
auto pred = [&name](pugi::xml_attribute attr) { return (name == attr.name()); };
|
||||
return handle.find_attribute(pred).value();
|
||||
return fmt::ToUTF8(reinterpret_cast<wxXmlNode*>(handle)->GetAttribute(fmt::FromUTF8(name)));
|
||||
}
|
||||
|
||||
std::string rXmlNode::GetNodeContent()
|
||||
{
|
||||
return handle.text().get();
|
||||
return fmt::ToUTF8(reinterpret_cast<wxXmlNode*>(handle)->GetNodeContent());
|
||||
}
|
||||
|
||||
rXmlDocument::rXmlDocument() : handle()
|
||||
rXmlDocument::rXmlDocument()
|
||||
{
|
||||
handle = reinterpret_cast<void *>(new wxXmlDocument());
|
||||
}
|
||||
|
||||
void rXmlDocument::Read(const std::string& data)
|
||||
rXmlDocument::rXmlDocument(const rXmlDocument& other)
|
||||
{
|
||||
handle.load_buffer(data.data(), data.size());
|
||||
handle = reinterpret_cast<void *>(new wxXmlDocument(*reinterpret_cast<wxXmlDocument*>(other.handle)));
|
||||
}
|
||||
|
||||
rXmlDocument &rXmlDocument::operator = (const rXmlDocument& other)
|
||||
{
|
||||
delete reinterpret_cast<wxXmlDocument*>(handle);
|
||||
handle = reinterpret_cast<void *>(new wxXmlDocument(*reinterpret_cast<wxXmlDocument*>(other.handle)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
rXmlDocument::~rXmlDocument()
|
||||
{
|
||||
delete reinterpret_cast<wxXmlDocument*>(handle);
|
||||
}
|
||||
|
||||
void rXmlDocument::Load(const std::string & path)
|
||||
{
|
||||
reinterpret_cast<wxXmlDocument*>(handle)->Load(fmt::FromUTF8(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<rXmlNode> rXmlDocument::GetRoot()
|
||||
{
|
||||
return std::make_shared<rXmlNode>(handle.root());
|
||||
return std::make_shared<rXmlNode>(reinterpret_cast<void*>(reinterpret_cast<wxXmlDocument*>(handle)->GetRoot()));
|
||||
}
|
||||
|
||||
|
||||
+12
-13
@@ -1,31 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PUGIXML_HEADER_ONLY
|
||||
#define PUGIXML_HEADER_ONLY 1
|
||||
#endif // !PUGIXML_HEADER_ONLY
|
||||
#include "pugixml.hpp"
|
||||
#undef PUGIXML_HEADER_ONLY
|
||||
|
||||
struct rXmlNode
|
||||
{
|
||||
rXmlNode();
|
||||
rXmlNode(const pugi::xml_node &);
|
||||
rXmlNode(void *);
|
||||
rXmlNode(const rXmlNode& other);
|
||||
rXmlNode &operator=(const rXmlNode& other);
|
||||
~rXmlNode();
|
||||
std::shared_ptr<rXmlNode> GetChildren();
|
||||
std::shared_ptr<rXmlNode> GetNext();
|
||||
std::string GetName();
|
||||
std::string GetAttribute( const std::string &name);
|
||||
std::string GetNodeContent();
|
||||
|
||||
pugi::xml_node handle;
|
||||
void *handle;
|
||||
bool ownPtr;
|
||||
};
|
||||
|
||||
struct rXmlDocument
|
||||
{
|
||||
rXmlDocument();
|
||||
rXmlDocument(const rXmlDocument& other) = delete;
|
||||
rXmlDocument &operator=(const rXmlDocument& other) = delete;
|
||||
void Read(const std::string& data);
|
||||
rXmlDocument(const rXmlDocument& other);
|
||||
rXmlDocument &operator=(const rXmlDocument& other);
|
||||
~rXmlDocument();
|
||||
void Load(const std::string & path);
|
||||
std::shared_ptr<rXmlNode> GetRoot();
|
||||
|
||||
pugi::xml_document handle;
|
||||
};
|
||||
void *handle;
|
||||
};
|
||||
@@ -1,124 +0,0 @@
|
||||
#ifndef MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
|
||||
#define MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
|
||||
|
||||
// Based on variant/recursive_wrapper.hpp from boost.
|
||||
//
|
||||
// Original license:
|
||||
//
|
||||
// Copyright (c) 2002-2003
|
||||
// Eric Friedman, Itay Maman
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See
|
||||
// accompanying file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
//namespace mapbox {
|
||||
//namespace util {
|
||||
namespace std {
|
||||
|
||||
template <typename T>
|
||||
class recursive_wrapper
|
||||
{
|
||||
|
||||
T* p_;
|
||||
|
||||
void assign(T const& rhs)
|
||||
{
|
||||
this->get() = rhs;
|
||||
}
|
||||
|
||||
public:
|
||||
using type = T;
|
||||
|
||||
/**
|
||||
* Default constructor default initializes the internally stored value.
|
||||
* For POD types this means nothing is done and the storage is
|
||||
* uninitialized.
|
||||
*
|
||||
* @throws std::bad_alloc if there is insufficient memory for an object
|
||||
* of type T.
|
||||
* @throws any exception thrown by the default constructur of T.
|
||||
*/
|
||||
recursive_wrapper()
|
||||
: p_(new T){}
|
||||
|
||||
~recursive_wrapper() noexcept { delete p_; }
|
||||
|
||||
recursive_wrapper(recursive_wrapper const& operand)
|
||||
: p_(new T(operand.get())) {}
|
||||
|
||||
recursive_wrapper(T const& operand)
|
||||
: p_(new T(operand)) {}
|
||||
|
||||
recursive_wrapper(recursive_wrapper&& operand)
|
||||
: p_(new T(std::move(operand.get()))) {}
|
||||
|
||||
recursive_wrapper(T&& operand)
|
||||
: p_(new T(std::move(operand))) {}
|
||||
|
||||
inline recursive_wrapper& operator=(recursive_wrapper const& rhs)
|
||||
{
|
||||
assign(rhs.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline recursive_wrapper& operator=(T const& rhs)
|
||||
{
|
||||
assign(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void swap(recursive_wrapper& operand) noexcept
|
||||
{
|
||||
T* temp = operand.p_;
|
||||
operand.p_ = p_;
|
||||
p_ = temp;
|
||||
}
|
||||
|
||||
recursive_wrapper& operator=(recursive_wrapper&& rhs) noexcept
|
||||
{
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
recursive_wrapper& operator=(T&& rhs)
|
||||
{
|
||||
get() = std::move(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T& get()
|
||||
{
|
||||
assert(p_);
|
||||
return *get_pointer();
|
||||
}
|
||||
|
||||
T const& get() const
|
||||
{
|
||||
assert(p_);
|
||||
return *get_pointer();
|
||||
}
|
||||
|
||||
T* get_pointer() { return p_; }
|
||||
|
||||
const T* get_pointer() const { return p_; }
|
||||
|
||||
operator T const&() const { return this->get(); }
|
||||
|
||||
operator T&() { return this->get(); }
|
||||
|
||||
}; // class recursive_wrapper
|
||||
|
||||
template <typename T>
|
||||
inline void swap(recursive_wrapper<T>& lhs, recursive_wrapper<T>& rhs) noexcept
|
||||
{
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
}
|
||||
//} // namespace util
|
||||
//} // namespace mapbox
|
||||
|
||||
#endif // MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
|
||||
@@ -1,103 +0,0 @@
|
||||
#include "sema.h"
|
||||
#include "sync.h"
|
||||
|
||||
void semaphore_base::imp_wait()
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
busy_wait();
|
||||
|
||||
const s32 value = m_value.load();
|
||||
|
||||
if (value > 0 && m_value.compare_and_swap_test(value, value - 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
const s32 value = m_value.fetch_sub(1);
|
||||
|
||||
if (value <= 0)
|
||||
{
|
||||
NtWaitForKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
}
|
||||
#else
|
||||
while (true)
|
||||
{
|
||||
// Try hard way
|
||||
const s32 value = m_value.op_fetch([](s32& value)
|
||||
{
|
||||
// Use sign bit to acknowledge waiter presence
|
||||
if (value && value > INT32_MIN)
|
||||
{
|
||||
value--;
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
// Remove sign bit
|
||||
value -= INT32_MIN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set sign bit
|
||||
value = INT32_MIN;
|
||||
}
|
||||
});
|
||||
|
||||
if (value >= 0)
|
||||
{
|
||||
// Signal other waiter to wake up or to restore sign bit
|
||||
futex(&m_value.raw(), FUTEX_WAKE_PRIVATE, 1, nullptr, nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
futex(&m_value.raw(), FUTEX_WAIT_PRIVATE, value, nullptr, nullptr, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void semaphore_base::imp_post(s32 _old)
|
||||
{
|
||||
verify("semaphore_base: overflow" HERE), _old < 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
NtReleaseKeyedEvent(nullptr, &m_value, false, nullptr);
|
||||
#else
|
||||
futex(&m_value.raw(), FUTEX_WAKE_PRIVATE, 1, nullptr, nullptr, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool semaphore_base::try_wait()
|
||||
{
|
||||
// Conditional decrement
|
||||
const s32 value = m_value.fetch_op([](s32& value)
|
||||
{
|
||||
if (value > 0)
|
||||
{
|
||||
value -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
return value > 0;
|
||||
}
|
||||
|
||||
bool semaphore_base::try_post(s32 _max)
|
||||
{
|
||||
// Conditional increment
|
||||
const s32 value = m_value.fetch_op([&](s32& value)
|
||||
{
|
||||
if (value < _max)
|
||||
{
|
||||
value += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
imp_post(value);
|
||||
}
|
||||
|
||||
return value < _max;
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
|
||||
// Lightweight semaphore helper class
|
||||
class semaphore_base
|
||||
{
|
||||
// Semaphore value
|
||||
atomic_t<s32> m_value;
|
||||
|
||||
void imp_wait();
|
||||
|
||||
void imp_post(s32 _old);
|
||||
|
||||
friend class semaphore_lock;
|
||||
|
||||
protected:
|
||||
explicit constexpr semaphore_base(s32 value)
|
||||
: m_value{value}
|
||||
{
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
// Load value
|
||||
const s32 value = m_value.load();
|
||||
|
||||
// Conditional decrement
|
||||
if (UNLIKELY(value <= 0 || !m_value.compare_and_swap_test(value, value - 1)))
|
||||
{
|
||||
imp_wait();
|
||||
}
|
||||
}
|
||||
|
||||
bool try_wait();
|
||||
|
||||
void post(s32 _max)
|
||||
{
|
||||
// Unconditional increment
|
||||
const s32 value = m_value.fetch_add(1);
|
||||
|
||||
if (UNLIKELY(value < 0 || value >= _max))
|
||||
{
|
||||
imp_post(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool try_post(s32 _max);
|
||||
|
||||
public:
|
||||
// Get current semaphore value
|
||||
s32 get() const
|
||||
{
|
||||
// Load value
|
||||
const s32 value = m_value;
|
||||
|
||||
// Return only positive value
|
||||
return value < 0 ? 0 : value;
|
||||
}
|
||||
};
|
||||
|
||||
// Lightweight semaphore template (default arguments define binary semaphore and Def == Max)
|
||||
template <s32 Max = 1, s32 Def = Max>
|
||||
class semaphore final : public semaphore_base
|
||||
{
|
||||
static_assert(Max >= 0, "semaphore<>: Max is out of bounds");
|
||||
static_assert(Def >= 0, "semaphore<>: Def is out of bounds");
|
||||
static_assert(Def <= Max, "semaphore<>: Def is too big");
|
||||
|
||||
using base = semaphore_base;
|
||||
|
||||
public:
|
||||
// Default constructor (recommended)
|
||||
constexpr semaphore()
|
||||
: base{Def}
|
||||
{
|
||||
}
|
||||
|
||||
// Explicit value constructor (not recommended)
|
||||
explicit constexpr semaphore(s32 value)
|
||||
: base{value}
|
||||
{
|
||||
}
|
||||
|
||||
// Obtain a semaphore
|
||||
void wait()
|
||||
{
|
||||
return base::wait();
|
||||
}
|
||||
|
||||
// Try to obtain a semaphore
|
||||
explicit_bool_t try_wait()
|
||||
{
|
||||
return base::try_wait();
|
||||
}
|
||||
|
||||
// Return a semaphore
|
||||
void post()
|
||||
{
|
||||
return base::post(Max);
|
||||
}
|
||||
|
||||
// Try to return a semaphore
|
||||
explicit_bool_t try_post()
|
||||
{
|
||||
return base::try_post(Max);
|
||||
}
|
||||
|
||||
// Get max semaphore value
|
||||
static constexpr s32 size()
|
||||
{
|
||||
return Max;
|
||||
}
|
||||
};
|
||||
|
||||
class semaphore_lock
|
||||
{
|
||||
semaphore_base& m_base;
|
||||
|
||||
void lock()
|
||||
{
|
||||
m_base.wait();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
m_base.post(INT32_MAX);
|
||||
}
|
||||
|
||||
friend class cond_variable;
|
||||
|
||||
public:
|
||||
explicit semaphore_lock(const semaphore_lock&) = delete;
|
||||
|
||||
semaphore_lock(semaphore_base& sema)
|
||||
: m_base(sema)
|
||||
{
|
||||
lock();
|
||||
}
|
||||
|
||||
~semaphore_lock()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
};
|
||||
@@ -1,148 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/* For internal use. Don't include. */
|
||||
|
||||
#include "types.h"
|
||||
#include "Atomic.h"
|
||||
#include "dynamic_library.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <time.h>
|
||||
#elif __linux__
|
||||
#include <errno.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/futex.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#endif
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef _WIN32
|
||||
DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResolution, BOOLEAN SetResolution, PULONG CurrentResolution));
|
||||
DYNAMIC_IMPORT("ntdll.dll", NtWaitForKeyedEvent, NTSTATUS(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout));
|
||||
DYNAMIC_IMPORT("ntdll.dll", NtReleaseKeyedEvent, NTSTATUS(HANDLE Handle, PVOID Key, BOOLEAN Alertable, PLARGE_INTEGER Timeout));
|
||||
DYNAMIC_IMPORT("ntdll.dll", NtDelayExecution, NTSTATUS(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval));
|
||||
#endif
|
||||
|
||||
#ifndef __linux__
|
||||
enum
|
||||
{
|
||||
FUTEX_PRIVATE_FLAG = 0,
|
||||
FUTEX_WAIT = 0,
|
||||
FUTEX_WAIT_PRIVATE = FUTEX_WAIT,
|
||||
FUTEX_WAKE = 1,
|
||||
FUTEX_WAKE_PRIVATE = FUTEX_WAKE,
|
||||
FUTEX_BITSET = 2,
|
||||
FUTEX_WAIT_BITSET = FUTEX_WAIT | FUTEX_BITSET,
|
||||
FUTEX_WAIT_BITSET_PRIVATE = FUTEX_WAIT_BITSET,
|
||||
FUTEX_WAKE_BITSET = FUTEX_WAKE | FUTEX_BITSET,
|
||||
FUTEX_WAKE_BITSET_PRIVATE = FUTEX_WAKE_BITSET,
|
||||
};
|
||||
#endif
|
||||
|
||||
inline int futex(int* uaddr, int futex_op, int val, const timespec* timeout, int* uaddr2, int val3)
|
||||
{
|
||||
#ifdef __linux__
|
||||
return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr, val3);
|
||||
#else
|
||||
static struct futex_map
|
||||
{
|
||||
struct waiter
|
||||
{
|
||||
int val;
|
||||
uint mask;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
std::mutex mutex;
|
||||
std::unordered_multimap<int*, waiter*, pointer_hash<int>> map;
|
||||
|
||||
int operator()(int* uaddr, int futex_op, int val, const timespec* timeout, int*, uint val3)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
switch (futex_op)
|
||||
{
|
||||
case FUTEX_WAIT:
|
||||
{
|
||||
val3 = -1;
|
||||
// Fallthrough
|
||||
}
|
||||
case FUTEX_WAIT_BITSET:
|
||||
{
|
||||
if (*(volatile int*)uaddr != val)
|
||||
{
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
waiter rec;
|
||||
rec.val = val;
|
||||
rec.mask = val3;
|
||||
const auto& ref = *map.emplace(uaddr, &rec);
|
||||
|
||||
int res = 0;
|
||||
|
||||
if (!timeout)
|
||||
{
|
||||
rec.cv.wait(lock, [&] { return !rec.mask; });
|
||||
}
|
||||
else if (futex_op == FUTEX_WAIT)
|
||||
{
|
||||
const auto nsec = std::chrono::nanoseconds(timeout->tv_nsec + timeout->tv_sec * 1000000000ull);
|
||||
|
||||
if (!rec.cv.wait_for(lock, nsec, [&] { return !rec.mask; }))
|
||||
{
|
||||
res = -1;
|
||||
errno = ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
map.erase(std::find(map.find(uaddr), map.end(), ref));
|
||||
return res;
|
||||
}
|
||||
|
||||
case FUTEX_WAKE:
|
||||
{
|
||||
val3 = -1;
|
||||
// Fallthrough
|
||||
}
|
||||
case FUTEX_WAKE_BITSET:
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
for (auto range = map.equal_range(uaddr); val && range.first != range.second; range.first++)
|
||||
{
|
||||
auto& entry = *range.first->second;
|
||||
|
||||
if (entry.mask & val3)
|
||||
{
|
||||
entry.cv.notify_one();
|
||||
entry.mask = 0;
|
||||
res++;
|
||||
val--;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
} g_futex;
|
||||
|
||||
return g_futex(uaddr, futex_op, val, timeout, uaddr2, val3);
|
||||
#endif
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user