This commit is contained in:
Evert Prants 2024-09-09 18:40:47 +03:00
commit ef307a5492
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
131 changed files with 7842 additions and 1957 deletions

View File

@ -51,7 +51,7 @@ jobs:
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: OpenStarbound-Dev-macOS-Intel
name: OpenStarbound-macOS-Intel
path: dist/*
build-arm:
@ -93,5 +93,5 @@ jobs:
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: OpenStarbound-Dev-macOS-Silicon
name: OpenStarbound-macOS-Silicon
path: dist/*

View File

@ -85,5 +85,5 @@ jobs:
- name: Upload Installer
uses: actions/upload-artifact@v4
with:
name: Installer
name: OpenStarbound-Windows-Installer
path: installer/*

View File

@ -9,13 +9,17 @@ Original README is as follows, if you're here reading it, you can skip the steps
This is a fork of Starbound. Contributions are welcome!
You **must** own a copy of Starbound to use it. Base game assets are not provided for obvious reasons.
It is still **work-in-progress**. You can download the very latest build below, or the occasional releases (though those aren't very up to date yet!)
## Nightly Builds
It is still **work-in-progress**.
At the moment, you must copy the game assets (**packed.pak**) from your normal Starbound install to the OpenStarbound assets directory.
## Installation
You can download a nightly build below, or the [latest release](https://github.com/OpenStarbound/OpenStarbound/releases/latest). At the moment, you must copy the game assets (**packed.pak**) from your normal Starbound install to the OpenStarbound assets directory before playing.
An installer is available for Windows. otherwise, extract the client/server zip for your platform and copy the game assets (packed.pak) to the OpenStarbound assets folder. the macOS releases currently lack the sbinit.config and folder structure that the Linux & Windows zips have, so you'll need to create those before running them. For macOS releases, it is recommended to build them from source (See guide below).
### Nightly Builds
These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab.
[**Windows**](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main):
[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/Installer.zip),
[Installer](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Installer.zip),
[Client](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Client.zip),
[Server](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_windows/main/OpenStarbound-Windows-Server.zip)
@ -27,8 +31,6 @@ At the moment, you must copy the game assets (**packed.pak**) from your normal S
[Intel](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Intel.zip),
[ARM](https://nightly.link/OpenStarbound/OpenStarbound/workflows/build_macos/main/OpenStarbound-Dev-macOS-Silicon.zip)
These link directly to the latest build from the [Actions](https://github.com/OpenStarbound/OpenStarbound/actions?query=branch%3Amain) tab.
## Changes
Note: Not every function from [StarExtensions](https://github.com/StarExtensions/StarExtensions) has been ported yet, but near-full compatibility with mods that use StarExtensions features is planned.
@ -42,8 +44,10 @@ Note: Not every function from [StarExtensions](https://github.com/StarExtensions
* These scripts can modify, read, patch and create new assets!
* Lua patch files now exist - **.patch.lua**
* These can patch JSON assets, as well as images!
### Bug Fixes
* Invalid character inventories are updated when loading in, allowing players to swap inventory mods with pre-existing characters.
### Misc
* Player functions for saving/loading, modifying the humanoid identity
* Player functions for saving/loading, modifying the humanoid identity, manipulating the inventory. [Documentation](https://github.com/OpenStarbound/OpenStarbound/blob/main/doc/lua/openstarbound.md)
* Character swapping (rewrite from StarExtensions, currently command-only: `/swap name` case-insensitive, only substring required)
* Custom user input support with a keybindings menu (rewrite from StarExtensions)
* Positional Voice Chat that works on completely vanilla servers, uses Opus for crisp, HD audio (rewrite from StarExtensions)
@ -52,11 +56,15 @@ Note: Not every function from [StarExtensions](https://github.com/StarExtensions
* **.woff2** fonts are much smaller than **.ttf**, [here's a web conversion tool](https://kombu.kanejaku.org/)!
* Experimental changes to the storage of directives in memory to reduce copying - can reduce their impact on frametimes when very long directives are present
* Works especially well when extremely long directives are used for "vanilla multiplayer-compatible" creations, like [generated clothing](https://silverfeelin.github.io/Starbound-NgOutfitGenerator/) or custom items/objects.
* Perfectly Generic Items will retain the data for what item they were if a mod is uninstalled, and will attempt to restore themselves if re-installed.
* Musical instruments have their own volume slider in the options menu.
* Players can use items while lounging
* Client-side tile placement prediction (rewrite from StarExtensions)
* You can also resize the placement area of tiles on the fly.
* Support for placing foreground tiles with a custom collision type (rewrite from StarExtensions, requires OpenSB server)
* Additionally, objects can be placed under non-solid foreground tiles.
* Admin characters have unlimited and unobstructed interaction/placement ranges
* Some minor polish to UI
* The Skybox's sun now matches the system type you're currently in.
@ -203,6 +211,44 @@ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:./" padsp ./starbound "$@"`
</details>
</details>
<details>
<summary><b>macOS</b></summary>
* First, you will need to have brew installed. Check out how to install [Homebrew](https://brew.sh/)
* Install cmake using `brew install cmake`
* Install ninja using `brew install ninja`
* Install pkg config using `brew install pkg-config`
* Next, install vcpkg by following the commands below.
* Run `cd ~`. This is just so that everything is local to here.
* Run ` git clone https://github.com/microsoft/vcpkg.git `
* Run `cd vcpkg && ./bootstrap-vcpkg.sh`
* Lastly, run ``` export VCPKG_ROOT=~/vcpkg && export PATH=$VCPKG_ROOT:$PATH ```
* This last command makes vcpkg added to the current terminal path. This lasts only while the terminal is active, and will have to be rerun for new terminal instances.
* Download the source code [here](https://github.com/OpenStarbound/OpenStarbound/archive/refs/heads/main.zip). This is the current code in main. Unpack the code to your downloads folder.
* Unpack the zip, and open it up. Navigate to OpenStarbound-main/source using the terminal -> `cd ~/Downloads/OpenStarbound-main`. Then navigate to the source folder, using `cd source`.
<details>
<summary>If using an Arm Mac</summary>
### macOS
To be written.
* While in the source folder in your terminal, run ` cmake --preset macos-arm-release `. This will get dependencies.
* After that command has finished, run ` cmake --build --preset macos-arm-release `. Wait for this to finish, then go to Finder. Navigate to the OpenStarbound-main folder using Finder.
* There will be a folder called <b>dist</b>. Inside dist will be your game files, but you still need to do a few more things to run it.
* First, in the OpenStarbound-main folder, there will be lib. Open lib, and open the osx folder. Inside is libsteam_api.dylib. Copy this file, and paste it into OpenStarbound-main/dist, so that it is in the same directory as the game files.
* Navigate back to OpenStarbound-main/lib/osx, and open up the folder arm64. Here, rename libdiscord_game_sdk.dylib to discord_game_sdk.dylib. The name must be that, or else the game won't be able to load.
* Grab the packed.pak file from your current Starbound install. It will be located in the assets folder. Copy that file into OpenStarbound-main/assets.
* Make a new file called sbinit.config (Make sure it is .config, not .somethingelse), and copy and paste in the sbinit.config text from above, located right underneath the title Building. Place sbinit.config inside OpenStarbound-main/dist. To make a new file, open the program called TextEdit on your mac, paste in the sbinit.config text from above, and click File (located at the very top of your screen), then click Save. It will prompt you, asking where to save it. Save As: sbinit.config, Where: Navigate to OpenStarbound-main/dist. Find the file you just saved, and rename it to get rid of the wrong extension, making sure the full name and extension looks like sbinit.config.
* You can now run the game by double clicking on the file called starbound in dist/. If it says unverified developer, open up the same folder where the game is in in the terminal. ` xattr -d com.apple.quarantine starbound `, which will get rid of the lock on the file. If that doesn't work, run ` sudo spctl --master-disable ` to allow all unverified apps.
</details>
<details>
<summary>If using an Intel Mac</summary>
* While in the source folder in your terminal, run ` cmake --preset macos-release `. This will get dependencies.
* After that command has finished, run ` cmake --build --preset macos-release `. Wait for this to finish, then go to Finder. Navigate to the OpenStarbound-main folder using Finder.
* There will be a folder called <b>dist</b>. Inside dist will be your game files, but you still need to do a few more things to run it.
* First, in the OpenStarbound-main folder, there will be lib. Open lib, and open the osx folder. Inside is libsteam_api.dylib. Copy this file, and paste it into OpenStarbound-main/dist, so that it is in the same directory as the game files.
* Navigate back to OpenStarbound-main/lib/osx, and open up the folder x64. Here, rename libdiscord_game_sdk.dylib to discord_game_sdk.dylib. The name must be that, or else the game won't be able to load.
* Grab the packed.pak file from your current Starbound install. It will be located in the assets folder. Copy that file into OpenStarbound-main/assets.
* Make a new file called sbinit.config (Make sure it is .config, not .somethingelse), and copy and paste in the sbinit.config text from above, located right underneath the title Building. Place sbinit.config inside OpenStarbound-main/dist. To make a new file, open the program called TextEdit on your mac, paste in the sbinit.config text from above, and click File (located at the very top of your screen), then click Save. It will prompt you, asking where to save it. Save As: sbinit.config, Where: Navigate to OpenStarbound-main/dist. Find the file you just saved, and rename it to get rid of the wrong extension, making sure the full name and extension looks like sbinit.config.
* You can now run the game by double clicking on the file called starbound in dist/. If it says unverified developer, open up the same folder where the game is in in the terminal. ` xattr -d com.apple.quarantine starbound `, which will get rid of the lock on the file. If that doesn't work, run ` sudo spctl --master-disable ` to allow all unverified apps.
</details>
</details>

View File

@ -9,5 +9,6 @@
"deployCinematicBase" : {
"scissor" : false,
"letterbox" : false
}
}
},
"postProcessLayers": []
}

View File

@ -1,3 +0,0 @@
{
"scale" : 1
}

View File

@ -1,3 +0,0 @@
{
"scale" : 1
}

View File

@ -1,3 +0,0 @@
{
"scale" : 1
}

View File

@ -1,5 +1,16 @@
{
"basicHelpText" : "Basic commands are: {}",
"adminHelpText" : "Admin commands are: {}",
"debugHelpText" : "Debug commands are: {}"
}
"basicHelpText": "Basic commands are: {}",
"adminHelpText": "Admin commands are: {}",
"debugHelpText": "Debug commands are: {}",
"openSbHelpText": "OpenSB commands are: {}",
"openSbDebugHelpText": "OpenSB Debug commands are: {}",
"openSbDebugCommands": {
"run": "Usage /run <lua>. Executes a script on the player and outputs the return value to chat."
},
"openSbCommands": {
"swap": "Usage /swap <name>. Swaps the current character, case-insensitive, only substring required.",
"respawninworld": "Usage /respawninworld. Sets the respawn flag for the current world until you teleport away."
}
}

View File

@ -4,7 +4,7 @@ function patch(original)
image:copyInto({0, 0}, original:process("?crop=0;0;236;96"))
local checkbox = image:process("?crop=19;26;117;35")
image:copyInto({119, 26}, checkbox) -- Anti-Aliasing
image:copyInto({19, 15}, checkbox) -- Object Lighting
image:copyInto({19, 15}, checkbox) -- New Lighting
image:copyInto({119, 15}, checkbox) -- Hardware Cursor
image:copyInto({119, 68}, image:process("?crop=19;68;117;87")) -- Camera Pan Speed

View File

@ -29,12 +29,16 @@ local function getMods(key)
if bindMods[1] then return bindMods end
end
local function finishBind(type, value)
local function finishBind(a, b)
widget.blur("snare")
snared = false
snareFinished{ type = type, value = value, mods = getMods(value) }
for i, mod in ipairs(mods) do
mod.active = false
if (type(a) == "table") then
snareFinished(a)
else
snareFinished{ type = a, value = b, mods = getMods(value) }
for i, mod in ipairs(mods) do
mod.active = false
end
end
end
@ -50,6 +54,8 @@ local function scanInputEvents()
return finishBind("key", data.key)
elseif type == "MouseButtonDown" then
return finishBind("mouse", data.mouseButton)
elseif type == "ControllerButtonDown" then
return finishBind{ type = "controller", value = data.controllerButton, controller = data.controller }
end
end
end
@ -127,9 +133,9 @@ function bindsToString(binds)
str = str .. v .. " + "
end
end
if bind.type == "key" then
str = str .. bind.value
elseif bind.type == "mouse" then
if bind.type == "controller" then
str = str .. "🎮 " .. bind.value
else
str = str .. bind.value
end
local _i = (i - 1) * 2
@ -362,7 +368,7 @@ local function initCallbacks()
end
function init()
--log = sb.logInfo
--log = chat and chat.addMessage or sb.logInfo
widget.clearListItems(CATEGORY_LIST_WIDGET)
initCallbacks()

View File

@ -35,9 +35,9 @@ function patch(config)
-- Create anti-aliasing toggle
shift(clone(layout, "multiTextureLabel", "antiAliasingLabel"), 98).value = "SUPER-SAMPLED AA"
shift(clone(layout, "multiTextureCheckbox", "antiAliasingCheckbox"), 99)
-- Create object lighting toggle
shift(clone(layout, "multiTextureLabel", "objectLightingLabel"), 0, -11).value = "NEW OBJECT LIGHTS"
shift(clone(layout, "multiTextureCheckbox", "objectLightingCheckbox"), 0, -11)
-- Create new lighting toggle
shift(clone(layout, "multiTextureLabel", "newLightingLabel"), 0, -11).value = "NEW LIGHTING"
shift(clone(layout, "multiTextureCheckbox", "newLightingCheckbox"), 0, -11)
-- Create hardware cursor toggle
shift(clone(layout, "multiTextureLabel", "hardwareCursorLabel"), 98, -11).value = "HARDWARE CURSOR"
shift(clone(layout, "multiTextureCheckbox", "hardwareCursorCheckbox"), 99, -11)

View File

@ -1,5 +1,8 @@
{
"password" : {
"hidden" : true
}
"panefeature" : {
"type" : "panefeature",
"anchor" : "center",
"positionLocked" : true
},
"password" : { "hidden" : true }
}

View File

@ -1,22 +0,0 @@
{
"paneLayout" : {
"group" : {
"position" : [8, 71]
},
"search" : {
"type" : "textbox",
"position" : [86, 71],
"hint" : "Search",
"maxWidth" : 50
},
"lblBandInput" : {
"position" : [3, 68]
},
"lblSearchInput" : {
"type" : "image",
"file" : "/interface/songbook/band.png",
"position" : [81, 68],
"zlevel" : -3
}
}
}

View File

@ -0,0 +1,12 @@
function patch(config)
local scrollBG = config.paneLayout.scrollBG
local scrollSize = assets.image(scrollBG.file):size()
config.paneLayout.search = {
type = "textbox",
position = {scrollBG.position[1] + 3,
scrollBG.position[2] + scrollSize[2] - 10},
hint = "^#999;Type here to search for a song",
maxWidth = scrollSize[1] - 6
}
return config
end

View File

@ -8,10 +8,10 @@ function patch(data)
data.backdropImages = jarray{
jarray{
jarray{0, 0},
"/interface/title/" .. (sb.makeRandomSource():randUInt(100) == 0 and "barst" or "starb") .. "ound.png",
"/interface/title/" .. (sb.makeRandomSource():randUInt(300) == 0 and "barst" or "starb") .. "ound.png",
0.5,
jarray{0.5, 0.5}
}
}
return data
end
end

View File

@ -1,5 +1,5 @@
{
"lighting" : {
"brightnessLimit" : 1.5
"brightnessLimit" : 1.4
}
}

View File

@ -1,6 +1,22 @@
--local function drop(color)
-- if type(color) == "table" then
-- for i = 1, #color do
-- color[i] = color[i] * 0.8
-- end
-- end
--end
function patch(object, path)
if object.pointLight ~= true and (object.lightColor or object.lightColors) then
object.lightType = "PointAsSpread"
return object;
return object
--elseif type(object.lightColor) == "table" then
-- drop(object.lightColor)
-- return object
--elseif type(object.lightColors) == "table" then
-- for i, v in pairs(object.lightColors) do
-- drop(v)
-- end
-- return object
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,7 @@
#version 140
in vec2 vertexPosition;
void main() {
gl_Position = vec4(vertexPosition, 0.0, 1.0);
}

View File

@ -1,13 +1,7 @@
{
"blitFrameBuffer" : "main",
"effectParameters" : {
"vertexRounding" : {
"type" : "bool",
"default" : false,
"uniform" : "vertexRounding"
}
},
"effectParameters" : {},
"effectTextures" : {},
"effectShaders" : {

View File

@ -1,4 +1,4 @@
#version 130
#version 140
uniform sampler2D texture0;
uniform sampler2D texture1;
@ -14,13 +14,13 @@ out vec4 outColor;
void main() {
vec4 texColor;
if (fragmentTextureIndex == 3)
texColor = texture2D(texture3, fragmentTextureCoordinate);
texColor = texture(texture3, fragmentTextureCoordinate);
else if (fragmentTextureIndex == 2)
texColor = texture2D(texture2, fragmentTextureCoordinate);
texColor = texture(texture2, fragmentTextureCoordinate);
else if (fragmentTextureIndex == 1)
texColor = texture2D(texture1, fragmentTextureCoordinate);
texColor = texture(texture1, fragmentTextureCoordinate);
else
texColor = texture2D(texture0, fragmentTextureCoordinate);
texColor = texture(texture0, fragmentTextureCoordinate);
if (texColor.a <= 0.0)
discard;

View File

@ -1,4 +1,4 @@
#version 130
#version 140
uniform vec2 textureSize0;
uniform vec2 textureSize1;
@ -6,7 +6,6 @@ uniform vec2 textureSize2;
uniform vec2 textureSize3;
uniform vec2 screenSize;
uniform mat3 vertexTransform;
uniform bool vertexRounding;
in vec2 vertexPosition;
in vec4 vertexColor;
@ -21,13 +20,6 @@ void main() {
vec2 screenPosition = (vertexTransform * vec3(vertexPosition, 1.0)).xy;
gl_Position = vec4(screenPosition / screenSize * 2.0 - 1.0, 0.0, 1.0);
if (vertexRounding) {
if (((vertexData >> 3) & 0x1) == 1)
screenPosition.x = round(screenPosition.x);
if (((vertexData >> 4) & 0x1) == 1)
screenPosition.y = round(screenPosition.y);
}
int vertexTextureIndex = vertexData & 0x3;
if (vertexTextureIndex == 3)
fragmentTextureCoordinate = vertexTextureCoordinate / textureSize3;

View File

@ -1,4 +1,4 @@
#version 130
#version 140
uniform sampler2D texture0;
uniform sampler2D texture1;
@ -27,7 +27,7 @@ vec4 cubic(float v) {
return vec4(x, y, z, w);
}
vec4 bicubicSample(sampler2D texture, vec2 texcoord, vec2 texscale) {
vec4 bicubicSample(sampler2D tex, vec2 texcoord, vec2 texscale) {
texcoord = texcoord - vec2(0.5, 0.5);
float fx = fract(texcoord.x);
@ -42,10 +42,10 @@ vec4 bicubicSample(sampler2D texture, vec2 texcoord, vec2 texscale) {
vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;
vec4 sample0 = texture2D(texture, vec2(offset.x, offset.z) * texscale);
vec4 sample1 = texture2D(texture, vec2(offset.y, offset.z) * texscale);
vec4 sample2 = texture2D(texture, vec2(offset.x, offset.w) * texscale);
vec4 sample3 = texture2D(texture, vec2(offset.y, offset.w) * texscale);
vec4 sample0 = texture(tex, vec2(offset.x, offset.z) * texscale);
vec4 sample1 = texture(tex, vec2(offset.y, offset.z) * texscale);
vec4 sample2 = texture(tex, vec2(offset.x, offset.w) * texscale);
vec4 sample3 = texture(tex, vec2(offset.y, offset.w) * texscale);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
@ -67,13 +67,13 @@ vec3 sampleLight(vec2 coord, vec2 scale) {
void main() {
vec4 texColor;
if (fragmentTextureIndex == 3)
texColor = texture2D(texture3, fragmentTextureCoordinate);
texColor = texture(texture3, fragmentTextureCoordinate);
else if (fragmentTextureIndex == 2)
texColor = texture2D(texture2, fragmentTextureCoordinate);
texColor = texture(texture2, fragmentTextureCoordinate);
else if (fragmentTextureIndex == 1)
texColor = texture2D(texture1, fragmentTextureCoordinate);
texColor = texture(texture1, fragmentTextureCoordinate);
else
texColor = texture2D(texture0, fragmentTextureCoordinate);
texColor = texture(texture0, fragmentTextureCoordinate);
if (texColor.a <= 0.0)
discard;

View File

@ -1,4 +1,4 @@
#version 130
#version 140
uniform vec2 textureSize0;
uniform vec2 textureSize1;

View File

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

View File

Before

Width:  |  Height:  |  Size: 178 B

After

Width:  |  Height:  |  Size: 178 B

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

View File

@ -1,4 +1,5 @@
-- Revert cursor frames if a mod replaced cursors.png with a SD version again
-- Otherwise, scale down our HD cursors
if assets.image("/cursors/cursors.png"):size()[1] == 64 then
local path = "/cursors/opensb/revert.cursor.patch"
assets.add(path, '{"scale":null}')
@ -8,6 +9,14 @@ if assets.image("/cursors/cursors.png"):size()[1] == 64 then
path = "/cursors/opensb/revert.frames.patch"
assets.add(path, '{"frameGrid":{"size":[16,16]}}')
assets.patch("/cursors/cursors.frames", path)
else
local cursors = assets.json("/cursors/cursors.frames:frameGrid.names")
local path = "/cursors/%s.cursor.patch"
for i = 1, #cursors do
for j = 1, #cursors[i] do
assets.add(string.format(path, cursors[i][j]), '{"scale":1}')
end
end
end
-- Add object patches
@ -15,4 +24,9 @@ local objects = assets.byExtension("object")
local path = "/objects/opensb/object.patch.lua"
for i = 1, #objects do
assets.patch(objects[i], path)
end
end
assets.patch(
"/interface/windowconfig/songbook.config",
"/interface/windowconfig/songbook_search_patch.lua"
)

View File

@ -21,6 +21,8 @@ command("run", function(src)
local success, result = pcall(result)
if not success then
return "^#f00;error: " .. result
elseif result == nil then
return nil
else
local success, printed = pcall(sb.printJson, result)
if not success then

View File

@ -3,41 +3,57 @@ local logHelp = "Available OpenStarbound server commands:\n"
local userHelp = logHelp .. "^cyan;"
local adminHelp = userHelp
local function cmd(name, description, permission, func)
local function cmd(meta, func)
local first = next(commands) == nil
logHelp = logHelp .. (first and name or ", " .. name)
userHelp = userHelp .. (first and name or ", ^cyan;" .. name)
adminHelp = adminHelp .. (first and name or ", ^cyan;" .. name)
local name = meta.name
local description = meta.description
local permission = meta.permission
if not meta.hidden then
logHelp = logHelp .. (first and name or ", " .. name)
userHelp = userHelp .. (first and name or ", ^cyan;" .. name)
adminHelp = adminHelp .. (first and name or ", ^cyan;" .. name)
end
local keyName = name:lower()
if permission == "tell" then
commands[keyName] = function(connectionId, ...)
commands[keyName] = {meta = meta, func = function(connectionId, ...)
return func(universe.isAdmin(connectionId), connectionId, ...)
end
end}
elseif permission == "admin" then
commands[keyName] = function(connectionId, ...)
commands[keyName] = {meta = meta, func = function(connectionId, ...)
local error = CommandProcessor.adminCheck(connectionId, description:sub(1, 1):lower() .. description:sub(2))
if error then
return error
else
return func(connectionId, ...)
end
end
end}
elseif permission == "user" then
commands[keyName] = func
commands[keyName] = {meta = meta, func = func}
else
error(string.format("Command '%s' has invalid permission", name))
end
end
cmd("openhelp", "Get help", "tell", function(isAdmin, connectionId)
cmd({
name = "openhelp",
description = "Get help",
permission = "tell"
},
function(isAdmin, connectionId)
return isAdmin and adminHelp or userHelp
end)
do
local objects = nil
cmd("packetTest", "Do science", "admin", function(connectionId)
cmd({
name = "packetTest",
description = "Do science",
permission = "admin",
hidden = true
},
function(connectionId)
if not objects then
objects = {}
local paths = root.assetsByExtension("object")
@ -69,7 +85,7 @@ function command(commandName, connectionId, args)
local command = commands[commandName:lower()]
if command then
local success, ret = pcall(command, connectionId, table.unpack(args))
local success, ret = pcall(command.func, connectionId, table.unpack(args))
if not success then
sb.logError("Error in OpenStarbound server command /%s: %s", commandName, ret)
return "command error: " .. ret

415
doc/lua/openstarbound.md Normal file
View File

@ -0,0 +1,415 @@
# Unsorted
These are functions that aren't in any specific table.
---
#### `Maybe<LuaFunction>, Maybe<String>` loadstring(`String` source, [`String` name, [`LuaValue` env]])
Compiles the provided **source** and returns it as a callable function.
If there are any syntax errors, returns `nil` and the error as a string instead.
- **name** is used for error messages, the default is the name of the script that called `loadstring`.
- **env** is used as the environment of the returned function, the default is `_ENV`.
---
# Root
The root table now contains extra asset bindings and bindings to return the tile variant that is used for material and matmods at any position.
---
#### `String[]` root.assetsByExtension(`String` extension)
Returns an array containing all assets with the specified file extension.
By the way, here's a list of every file extension the game does Special Things™ for when loading assets.
<details><summary><b>File Extensions</b></summary>
- Items: `item`, `liqitem`, `matitem`, `miningtool`, `flashlight`, `wiretool`, `beamaxe`, `tillingtool`, `painttool`, `harvestingtool`, `head`, `chest`, `legs`, `back`, `currency`, `consumable`, `blueprint`, `inspectiontool`, `instrument`, `thrownitem`, `unlock`, `activeitem`, `augment`
- Materials: `material`, `matmod`
- Liquids: `liquid`
- NPCs: `npctype`
- Tenants: `tenant`
- Objects: `object`
- Vehicles: `vehicle`
- Monsters: `monstertype`, `monsterpart`, `monsterskill`, `monstercolors`
- Plants: `modularstem`, `modularfoliage`, `grass`, `bush`
- Projectiles: `projectile`
- Particles: `particle`
- Name Gen: `namesource`
- AI Missions: `aimission`
- Quests: `questtemplate`
- Radio Messages: `radiomessages`
- Spawn Types: `spawntypes`
- Species: `species`
- Stagehand: `stagehand`
- Behaviors: `nodes`, `behavior`
- Biomes: `biome`, `weather`
- Terrain: `terrain`
- Treasure: `treasurepools`, `treasurechests`
- Codex Entries: `codex`
- Collections: `collection`
- Statistics: `event`, `achievement`
- Status Effects: `statuseffect`
- Functions: `functions`, `2functions`, `configfunctions`
- Tech: `tech`
- Damage: `damage`
- Dances: `dance`
- Effect Sources: `effectsource`
- Command Macros: `macros`
- Recipes: `recipe`
</details>
#### `String` root.assetData(`String` path)
Returns the raw data of an asset.
#### `String, Maybe<LuaTable>` root.assetOrigin(`String` path, [`bool` getPatches])
Returns the asset source path of an asset, or nil if the asset doesn't exist. If you specify getPatches as true then also returns the patches for the asset as an array, each element containing the source path and patch path in indexes 1 and 2 respectively.
#### `LuaTable` root.assetSourcePaths([`bool` withMetadata])
Without metadata: Returns an array with all the asset source paths.
With metadata: Returns a table, key/value being source path/metadata.
#### `?` root.assetImage(`String` image)
*TODO*
#### `JsonArray` root.assetPatches(`String` asset)
Returns a list of asset sources which patch the specified asset and the paths to those patches.
---
#### `Json` root.getConfiguration(`String` key)
Gets a configuration value in `/storage/starbound.config`.
#### `Json` root.getConfigurationPath(`String` path)
Gets a configuration value in `/storage/starbound.config` by path.
*Both getters will error if you try to get `title`, as that can contain the player's saved server login.*
#### `Json` root.setConfiguration(`String` key, `Json` value)
Sets a configuration value in `/storage/starbound.config`.
#### `Json` root.setConfigurationPath(`String` path, `Json` value)
Sets a configuration value in `/storage/starbound.config` by path.
*Both setters will error if you try to set `safeScripts`, as that can break Starbound's sandbox.*
---
#### `JsonArray` root.allRecipes()
Returns all recipes.
---
# Player
The player table now contains bindings which contains functions to save/load, access and modify the player's identity, mode, aim, emote and more.
---
#### `Json` player.save()
Serializes the player to Json the same way Starbound does for disk storage and returns it.
#### `void` player.load(`Json` save)
Reloads the player from a Json **save**. This will reset active ScriptPanes and scripts running on the player.
---
#### `String` player.name()
Returns the player's name.
#### `void` player.setName(`String` name)
Sets the player's name.
---
#### `String` player.description()
Returns the player's description.
#### `void` player.setDescription(`String` description)
Sets the player's description. The new description will not be networked buntil the player warps or respawns.
---
#### `void` player.setSpecies(`String` species)
Sets the player's species. Must be a valid species.
---
#### `void` player.setGender(`String` gender)
Sets the player's gender.
---
#### `String` player.imagePath()
If the player has a custom humanoid image path set, returns it. otherwise, returns `nil`.
#### `void` player.setImagePath(`String` imagePath)
Sets the player's image path. Specify `nil` to remove the image path.
---
#### `Personality` player.personality()
Returns the player's personality as a `table` containing a `string` idle, `string` armIdle, `Vec2F` headOffset and `Vec2F` armOffset.
#### `void` player.setPersonality(`Personality` personality)
Sets the player's personality. The **personality** must be a `table` containing at least one value as returned by `player.personality()`.
---
#### `String` player.bodyDirectives()
Returns the player's body directives.
#### `void` player.setBodyDirectives(`String` bodyDirectives)
Sets the player's body directives.
---
#### `String` player.emoteDirectives()
Returns the player's emote directives.
#### `void` player.setEmoteDirectives(`String` emoteDirectives)
Sets the player's emote directives.
---
#### `String` player.hair()
Returns the player's hair group.
#### `void` player.setHair(`String` hairGroup)
Sets the player's hair group.
---
#### `String` player.hairType()
Returns the player's hair type.
#### `void` player.setHairType(`String` hairType)
Sets the player's hair type.
---
#### `String` player.hairDirectives()
Returns the player's hair directives.
#### `void` player.setHairDirectives(`String` hairDirectives)
Sets the player's hair directives.
---
#### `String` player.facialHair()
Returns the player's facial hair type. Same as player.facialHairType?
#### `void` player.setFacialHair(`String` facialHairGroup, `String` facialHairType, `String` facialHairDirectives)
Sets the player's facial hair group, type, and directives.
---
#### `String` player.facialHairType()
Returns the player's facial hair type.
#### `void` player.setFacialHairType(`String` facialHairType)
Sets the player's facial hair type.
---
#### `String` player.facialHairGroup()
Returns the player's facial hair group.
#### `void` player.setFacialHairGroup(`String` facialHairGroup)
Sets the player's facial hair group.
---
#### `String` player.facialHairDirectives()
Returns the player's facial hair directives.
#### `void` player.setFacialHairDirectives(`String` facialHairDirectives)
Sets the player's facial hair directives.
---
#### `String` player.facialMask()
Returns the player's facial mask group.
#### `void` player.setFacialMask(`String` facialMaskGroup, `String` facialMaskType, `String` facialMaskDirectives)
Sets the player's facial mask group, type, and directives.
---
#### `String` player.facialMaskDirectives()
Returns the player's facial mask directives.
#### `void` player.setFacialMaskDirectives(`String` facialMaskDirectives)
Sets the player's facial mask directives.
---
#### `PlayerMode` player.mode()
Returns the player's mode.
#### `void` player.setMode(`String` mode)
Sets the player's mode. **mode** must be either `"casual"`, `"survival"` or `"hardcore"`.
---
#### `Color` player.favoriteColor()
Returns the player's favorite color.
It is used for the beam shown when wiring, placing, and highlighting with beam-tools (Matter Manipulator).
#### `void` player.setFavoriteColor(`Color` color)
Sets the player's favorite color. **color** can have an optional fourth value for transparency.
---
#### `Vec2F` player.aimPosition()
Returns the player's aim position.
---
#### `void` player.emote(`String` emote, [`float` cooldown])
Makes the player do an emote with the default cooldown unless a **cooldown** is specified.
#### `String, float` player.currentEmote()
Returns the player's current emote and the seconds left in it.
---
#### `unsigned` player.actionBarGroup()
Returns the player's active action bar.
#### `void` player.setActionBarGroup(`unsigned` barId)
Sets the player's active action bar.
#### `Variant<unsigned, EssentialItem>` player.selectedActionBarSlot()
Returns the player's selected action bar slot.
#### `void` player.setSelectedActionBarSlot(`Variant<unsigned, EssentialItem>` slot)
Sets the player's selected action bar slot.
#### `void` player.setDamageTeam(`DamageTeam` team)
Sets the player's damage team. This must be called every frame to override the current damage team that the server has given the player (normally controlled by /pvp)
---
#### `void` player.say(`String` line)
Makes the player say a string.
---
#### `Json` player.humanoidIdentity()
Returns the specific humanoid identity of the player, containing information such as hair style and idle pose.
#### `void` player.setHumanoidIdentity(`Json` humanoidIdentity)
Sets the specific humanoid identity of the player.
---
#### `ItemDescriptor` player.item(`ItemSlot` itemSlot)
Returns the contents of the specified itemSlot.
#### `void` player.setItem(`ItemSlot` itemSlot, `ItemDescriptor` item)
Puts the specified item into the specified itemSlot.
Item slots in item bags are structured like so: `{String bagName, int slot}`
---
#### `int` player.itemBagSize(`String` itemBagName)
Returns the size of an item bag.
#### `bool` player.itemAllowedInBag(`String` itemBagName, `ItemDescriptor` item)
Returns whether the specified item can enter the specified item bag.
---
#### `ActionBarLink` player.actionBarSlotLink(`int` slot, `String` hand)
Returns the contents of the specified action bar link slot's specified hand.
#### `bool` player.setActionBarSlotLink(`String` itemBagName, `ItemDescriptor` item)
Returns whether the specified item can enter the specified item bag.
---
#### `Float` player.interactRadius()
Returns the player's interact radius.
#### `void` player.setInteractRadius(`Float` interactRadius)
Sets the player's interact radius. This does not persist upon returning to the main menu.
---
#### `JsonArray` player.availableRecipes()
Returns all the recipes the player can craft with their currently held items and currencies.
---

View File

@ -21,6 +21,7 @@ cp \
lib/linux/libsteam_api.so \
scripts/ci/linux/sbinit.config \
scripts/ci/linux/run-client.sh \
scripts/steam_appid.txt \
client_distribution/linux/
mkdir -p server_distribution
@ -33,11 +34,21 @@ touch server_distribution/mods/mods_go_here
mkdir -p server_distribution/linux
# makes the server function on older Linux versions (this is so stupid)
nm --dynamic --undefined-only --with-symbol-versions dist/starbound_server | grep GLIBC_2.29
./scripts/ci/linux/patchelf dist/starbound_server \
--clear-symbol-version exp \
--clear-symbol-version exp2 \
--clear-symbol-version log \
--clear-symbol-version log2 \
--clear-symbol-version pow
cp \
dist/starbound_server \
dist/btree_repacker \
scripts/ci/linux/run-server.sh \
scripts/ci/linux/sbinit.config \
scripts/steam_appid.txt \
server_distribution/linux/
tar -cvf dist.tar dist

BIN
scripts/ci/linux/patchelf Executable file

Binary file not shown.

View File

@ -24,4 +24,5 @@ cp \
dist/planet_mapgen \
scripts/ci/macos/sbinit.config \
scripts/ci/macos/run-server.sh \
scripts/steam_appid.txt \
client_distribution/osx/

View File

@ -8,6 +8,7 @@ mkdir %client%\mods
mkdir %client%\logs
mkdir %client%\assets
mkdir %client%\win
echo 211820 > %client%\win\steam_appid.txt
set server=server_distribution
if exist %server% rmdir %server% /S /Q

View File

@ -136,6 +136,8 @@ endif()
option(STAR_LUA_APICHECK "Use lua api checks" OFF)
option(STAR_USE_JEMALLOC "Use jemalloc allocators" OFF)
option(STAR_USE_MIMALLOC "Use mimalloc allocators" OFF)
option(STAR_USE_RPMALLOC "Use rpmalloc allocators" OFF)
# Report all the discovered system / environment settings and all options.
@ -174,6 +176,8 @@ endif()
message(STATUS "Using Lua API checks: ${STAR_LUA_APICHECK}")
message(STATUS "Using jemalloc: ${STAR_USE_JEMALLOC}")
message(STATUS "Using mimalloc: ${STAR_USE_MIMALLOC}")
message(STATUS "Using rpmalloc: ${STAR_USE_RPMALLOC}")
# Set C defines and cmake variables based on the build settings we have now
# determined...
@ -244,6 +248,10 @@ endif()
if(STAR_USE_JEMALLOC)
add_definitions(-DSTAR_USE_JEMALLOC)
elseif(STAR_USE_MIMALLOC)
add_definitions(-DSTAR_USE_MIMALLOC)
elseif(STAR_USE_RPMALLOC)
add_definitions(-DSTAR_USE_RPMALLOC -DENABLE_PRELOAD)
endif()
# Set C/C++ compiler flags based on build environment...
@ -449,6 +457,11 @@ if(STAR_USE_JEMALLOC)
set(STAR_EXT_LIBS ${JEMALLOC_LIBRARY})
endif()
if (STAR_USE_MIMALLOC)
find_package(mimalloc CONFIG REQUIRED)
set(STAR_EXT_LIBS ${STAR_EXT_LIBS} $<IF:$<TARGET_EXISTS:mimalloc-static>,mimalloc-static,mimalloc>)
endif()
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)
find_package(Freetype REQUIRED)

View File

@ -31,7 +31,8 @@
"VCPKG_TARGET_TRIPLET": "x64-windows-mixed-md",
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL",
"CMAKE_INCLUDE_PATH": "${sourceParentDir}/lib/windows/include",
"CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/windows"
"CMAKE_LIBRARY_PATH": "${sourceParentDir}/lib/windows",
"STAR_USE_RPMALLOC": true
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {

View File

@ -18,6 +18,10 @@ namespace Star {
LPWSTR* argsList = CommandLineToArgvW(GetCommandLineW(), &nArgs); \
Star::StringList args; \
for (int i = 0; i < nArgs; ++i) args.append(Star::String(argsList[i])); \
if (IsDebuggerPresent() && AllocConsole()) { \
freopen("CONOUT$", "w", stdout); \
freopen("CONOUT$", "w", stderr); \
} \
return Star::runMainApplication(Star::make_unique<ApplicationClass>(), args); \
}

View File

@ -90,23 +90,23 @@ Maybe<Key> keyFromSdlKeyCode(SDL_Keycode sym) {
{SDLK_y, Key::Y},
{SDLK_z, Key::Z},
{SDLK_DELETE, Key::Delete},
{SDLK_KP_0, Key::Kp0},
{SDLK_KP_1, Key::Kp1},
{SDLK_KP_2, Key::Kp2},
{SDLK_KP_3, Key::Kp3},
{SDLK_KP_4, Key::Kp4},
{SDLK_KP_5, Key::Kp5},
{SDLK_KP_6, Key::Kp6},
{SDLK_KP_7, Key::Kp7},
{SDLK_KP_8, Key::Kp8},
{SDLK_KP_9, Key::Kp9},
{SDLK_KP_PERIOD, Key::Kp_period},
{SDLK_KP_DIVIDE, Key::Kp_divide},
{SDLK_KP_MULTIPLY, Key::Kp_multiply},
{SDLK_KP_MINUS, Key::Kp_minus},
{SDLK_KP_PLUS, Key::Kp_plus},
{SDLK_KP_ENTER, Key::Kp_enter},
{SDLK_KP_EQUALS, Key::Kp_equals},
{SDLK_KP_0, Key::Keypad0},
{SDLK_KP_1, Key::Keypad1},
{SDLK_KP_2, Key::Keypad2},
{SDLK_KP_3, Key::Keypad3},
{SDLK_KP_4, Key::Keypad4},
{SDLK_KP_5, Key::Keypad5},
{SDLK_KP_6, Key::Keypad6},
{SDLK_KP_7, Key::Keypad7},
{SDLK_KP_8, Key::Keypad8},
{SDLK_KP_9, Key::Keypad9},
{SDLK_KP_PERIOD, Key::KeypadPeriod},
{SDLK_KP_DIVIDE, Key::KeypadDivide},
{SDLK_KP_MULTIPLY, Key::KeypadMultiply},
{SDLK_KP_MINUS, Key::KeypadMinus},
{SDLK_KP_PLUS, Key::KeypadPlus},
{SDLK_KP_ENTER, Key::KeypadEnter},
{SDLK_KP_EQUALS, Key::KeypadEquals},
{SDLK_UP, Key::Up},
{SDLK_DOWN, Key::Down},
{SDLK_RIGHT, Key::Right},
@ -131,6 +131,15 @@ Maybe<Key> keyFromSdlKeyCode(SDL_Keycode sym) {
{SDLK_F13, Key::F13},
{SDLK_F14, Key::F14},
{SDLK_F15, Key::F15},
{SDLK_F16, Key::F16},
{SDLK_F17, Key::F17},
{SDLK_F18, Key::F18},
{SDLK_F19, Key::F19},
{SDLK_F20, Key::F20},
{SDLK_F21, Key::F21},
{SDLK_F22, Key::F22},
{SDLK_F23, Key::F23},
{SDLK_F24, Key::F24},
{SDLK_NUMLOCKCLEAR, Key::NumLock},
{SDLK_CAPSLOCK, Key::CapsLock},
{SDLK_SCROLLLOCK, Key::ScrollLock},
@ -235,7 +244,9 @@ public:
SDL_free(basePath);
}
#if SDL_VERSION_ATLEAST(2, 0, 18)
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif
m_signalHandler.setHandleInterrupt(true);
m_signalHandler.setHandleFatal(true);
@ -303,6 +314,10 @@ public:
int height;
SDL_GetWindowSize(m_sdlWindow, &width, &height);
m_windowSize = Vec2U(width, height);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
m_sdlGlContext = SDL_GL_CreateContext(m_sdlWindow);
if (!m_sdlGlContext)

View File

@ -81,7 +81,11 @@ void PcP2PNetworkingService::setJoinRemote(HostAddressWithPort location) {
setJoinLocation(JoinRemote(location));
}
void Star::PcP2PNetworkingService::setActivityData([[maybe_unused]] String const& title, [[maybe_unused]] Maybe<pair<uint16_t, uint16_t>> party) {
void Star::PcP2PNetworkingService::setActivityData(
[[maybe_unused]] const char* title,
[[maybe_unused]] const char* details,
[[maybe_unused]] int64_t startTime,
[[maybe_unused]] Maybe<pair<uint16_t, uint16_t>> party) {
#ifdef STAR_ENABLE_DISCORD_INTEGRATION
MutexLocker discordLocker(m_state->discordMutex);
#endif
@ -92,19 +96,25 @@ void Star::PcP2PNetworkingService::setActivityData([[maybe_unused]] String const
if (m_discordUpdatingActivity)
return;
if (title != m_discordActivityTitle || party != m_discordPartySize || m_discordForceUpdateActivity) {
if (title != m_discordActivityTitle
|| details != m_discordActivityDetails
|| startTime != m_discordActivityStartTime || party != m_discordPartySize || m_discordForceUpdateActivity) {
m_discordForceUpdateActivity = false;
m_discordPartySize = party;
m_discordActivityTitle = title;
m_discordActivityDetails = details;
m_discordActivityStartTime = startTime;
discord::Activity activity = {};
activity.SetType(discord::ActivityType::Playing);
activity.SetName("Starbound");
activity.SetState(title.utf8Ptr());
if (auto p = party) {
activity.GetParty().GetSize().SetCurrentSize(p->first);
activity.GetParty().GetSize().SetMaxSize(p->second);
activity.SetState(title);
activity.SetDetails(details);
activity.GetTimestamps().SetStart(startTime);
if (party) {
auto& size = activity.GetParty().GetSize();
size.SetCurrentSize(party->first);
size.SetMaxSize(party->second);
}
if (auto lobby = m_discordServerLobby)

View File

@ -18,7 +18,7 @@ public:
void setJoinUnavailable() override;
void setJoinLocal(uint32_t capacity) override;
void setJoinRemote(HostAddressWithPort location) override;
void setActivityData(String const& title, Maybe<pair<uint16_t, uint16_t>>) override;
void setActivityData(const char* title, const char* details, int64_t startTime, Maybe<pair<uint16_t, uint16_t>>) override;
MVariant<P2PNetworkingPeerId, HostAddressWithPort> pullPendingJoin() override;
Maybe<pair<String, RpcPromiseKeeper<P2PJoinRequestReply>>> pullJoinRequest() override;
@ -125,6 +125,8 @@ private:
HashMap<discord::UserId, DiscordP2PSocket*> m_discordOpenSockets;
String m_discordActivityTitle;
String m_discordActivityDetails;
int64_t m_discordActivityStartTime = 0;
Maybe<pair<uint16_t, uint16_t>> m_discordPartySize;
bool m_discordForceUpdateActivity = false;
bool m_discordUpdatingActivity = false;

View File

@ -8,7 +8,7 @@ namespace Star {
size_t const MultiTextureCount = 4;
char const* DefaultVertexShader = R"SHADER(
#version 130
#version 140
uniform vec2 textureSize0;
uniform vec2 textureSize1;
@ -49,7 +49,7 @@ void main() {
)SHADER";
char const* DefaultFragmentShader = R"SHADER(
#version 130
#version 140
uniform sampler2D texture0;
uniform sampler2D texture1;
@ -65,13 +65,13 @@ out vec4 outColor;
void main() {
vec4 texColor;
if (fragmentTextureIndex == 3)
texColor = texture2D(texture3, fragmentTextureCoordinate);
texColor = texture(texture3, fragmentTextureCoordinate);
else if (fragmentTextureIndex == 2)
texColor = texture2D(texture2, fragmentTextureCoordinate);
texColor = texture(texture2, fragmentTextureCoordinate);
else if (fragmentTextureIndex == 1)
texColor = texture2D(texture1, fragmentTextureCoordinate);
texColor = texture(texture1, fragmentTextureCoordinate);
else
texColor = texture2D(texture0, fragmentTextureCoordinate);
texColor = texture(texture0, fragmentTextureCoordinate);
if (texColor.a <= 0.0)
discard;
@ -80,9 +80,19 @@ void main() {
}
)SHADER";
/*
static void GLAPIENTRY GlMessageCallback(GLenum, GLenum type, GLuint, GLenum, GLsizei, const GLchar* message, const void* renderer) {
if (type == GL_DEBUG_TYPE_ERROR) {
Logger::error("GL ERROR: {}", message);
__debugbreak();
}
}
*/
OpenGlRenderer::OpenGlRenderer() {
if (glewInit() != GLEW_OK)
throw RendererException("Could not initialize GLEW");
auto glewResult = glewInit();
if (glewResult != GLEW_OK && glewResult != GLEW_ERROR_NO_GLX_DISPLAY)
throw RendererException::format("Could not initialize GLEW: {}", (char*)glewGetErrorString(glewResult));
if (!GLEW_VERSION_2_0)
throw RendererException("OpenGL 2.0 not available!");
@ -94,10 +104,11 @@ OpenGlRenderer::OpenGlRenderer() {
(const char*)glGetString(GL_SHADING_LANGUAGE_VERSION));
glClearColor(0.0, 0.0, 0.0, 1.0);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
//glEnable(GL_DEBUG_OUTPUT);
//glDebugMessageCallback(GlMessageCallback, this);
m_whiteTexture = createGlTexture(Image::filled({1, 1}, Vec4B(255, 255, 255, 255), PixelFormat::RGBA32),
TextureAddressing::Clamp,
@ -142,12 +153,32 @@ OpenGlRenderer::GlFrameBuffer::GlFrameBuffer(Json const& fbConfig) : config(fbCo
GLenum target = multisample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
glBindTexture(target, texture->glTextureId());
Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 }));
sizeDiv = config.getUInt("sizeDiv", 1);
Vec2U size = jsonToVec2U(config.getArray("size", { 256, 256 })) / sizeDiv;
if (multisample)
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, size[0], size[1], GL_TRUE);
else
else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size[0], size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
}
auto addressing = TextureAddressingNames.getLeft(config.getString("textureAddressing", "clamp"));
auto filtering = TextureFilteringNames.getLeft(config.getString("textureFiltering", "nearest"));
if (addressing == TextureAddressing::Clamp) {
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else {
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
if (!multisample) {
if (filtering == TextureFiltering::Nearest) {
glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} else {
glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
}
glGenFramebuffers(1, &id);
if (!id)
@ -173,6 +204,7 @@ void OpenGlRenderer::loadConfig(Json const& config) {
for (auto& pair : config.getObject("frameBuffers", {})) {
Json config = pair.second;
config = config.set("multisample", m_multiSampling);
Logger::info("Creating framebuffer {}", pair.first);
m_frameBuffers[pair.first] = make_ref<GlFrameBuffer>(config);
}
@ -246,15 +278,16 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf
auto& effect = m_effects.emplace(name, Effect()).first->second;
effect.program = m_program;
effect.config = effectConfig;
effect.includeVBTextures = effectConfig.getBool("includeVBTextures",true);
m_currentEffect = &effect;
setupGlUniforms(effect);
setupGlUniforms(effect, m_screenSize);
for (auto const& p : effectConfig.getObject("effectParameters", {})) {
EffectParameter effectParameter;
effectParameter.parameterUniform = glGetUniformLocation(m_program, p.second.getString("uniform").utf8Ptr());
if (effectParameter.parameterUniform == -1) {
Logger::warn("OpenGL20 effect parameter '{}' has no associated uniform, skipping", p.first);
Logger::warn("OpenGL20 effect parameter '{}' in effect '{}' has no associated uniform, skipping", p.first, name);
} else {
String type = p.second.getString("type");
if (type == "bool") {
@ -296,7 +329,7 @@ void OpenGlRenderer::loadEffectConfig(String const& name, Json const& effectConf
// Assign each texture parameter a texture unit starting with MultiTextureCount, the first
// few texture units are used by the primary textures being drawn. Currently,
// maximum texture units are not checked.
unsigned parameterTextureUnit = MultiTextureCount;
unsigned parameterTextureUnit = effect.includeVBTextures ? MultiTextureCount : 0;
for (auto const& p : effectConfig.getObject("effectTextures", {})) {
EffectTexture effectTexture;
@ -383,19 +416,39 @@ bool OpenGlRenderer::switchEffectConfig(String const& name) {
if (auto blitFrameBufferId = effect.config.optString("blitFrameBuffer"))
blitGlFrameBuffer(getGlFrameBuffer(*blitFrameBufferId));
if (auto frameBufferId = effect.config.optString("frameBuffer"))
switchGlFrameBuffer(getGlFrameBuffer(*frameBufferId));
else {
auto effectScreenSize = m_screenSize;
if (auto frameBufferId = effect.config.optString("frameBuffer")) {
auto buf = getGlFrameBuffer(*frameBufferId);
switchGlFrameBuffer(buf);
effectScreenSize = m_screenSize / (buf->sizeDiv);
} else {
m_currentFrameBuffer.reset();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}
glUseProgram(m_program = effect.program);
setupGlUniforms(effect);
setupGlUniforms(effect, effectScreenSize);
m_currentEffect = &effect;
setEffectParameter("vertexRounding", m_multiSampling > 0);
if (auto fbts = effect.config.optArray("frameBufferTextures")) {
for (auto const& fbt : *fbts) {
if (auto frameBufferId = fbt.optString("framebuffer")) {
auto textureUniform = fbt.getString("texture");
auto ptr = m_currentEffect->textures.ptr(textureUniform);
if (ptr) {
if (!ptr->textureValue || ptr->textureValue->textureId == 0) {
auto texture = getGlFrameBuffer(*frameBufferId)->texture;
ptr->textureValue = texture;
if (ptr->textureSizeUniform != -1) {
auto textureSize = ptr->textureValue->glTextureSize();
glUniform2f(ptr->textureSizeUniform, textureSize[0], textureSize[1]);
}
}
}
}
}
}
return true;
}
@ -494,12 +547,13 @@ void OpenGlRenderer::setScreenSize(Vec2U screenSize) {
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
for (auto& frameBuffer : m_frameBuffers) {
unsigned sizeDiv = frameBuffer.second->sizeDiv;
if (unsigned multisample = frameBuffer.second->multisample) {
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, frameBuffer.second->texture->glTextureId());
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, m_screenSize[0], m_screenSize[1], GL_TRUE);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisample, GL_RGBA8, m_screenSize[0] / sizeDiv, m_screenSize[1] / sizeDiv, GL_TRUE);
} else {
glBindTexture(GL_TEXTURE_2D, frameBuffer.second->texture->glTextureId());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0], m_screenSize[1], 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_screenSize[0] / sizeDiv, m_screenSize[1] / sizeDiv, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
}
}
}
@ -694,6 +748,10 @@ Vec2U OpenGlRenderer::GlLoneTexture::glTextureCoordinateOffset() const {
return Vec2U();
}
OpenGlRenderer::GlRenderBuffer::GlRenderBuffer() {
glGenVertexArrays(1, &vertexArray);
}
OpenGlRenderer::GlRenderBuffer::~GlRenderBuffer() {
for (auto const& texture : usedTextures) {
if (auto gt = as<GlGroupedTexture>(texture.get()))
@ -701,6 +759,7 @@ OpenGlRenderer::GlRenderBuffer::~GlRenderBuffer() {
}
for (auto const& vb : vertexBuffers)
glDeleteBuffers(1, &vb.vertexBuffer);
glDeleteVertexArrays(1, &vertexArray);
}
void OpenGlRenderer::GlRenderBuffer::set(List<RenderPrimitive>& primitives) {
@ -715,7 +774,7 @@ void OpenGlRenderer::GlRenderBuffer::set(List<RenderPrimitive>& primitives) {
List<GLuint> currentTextures;
List<Vec2U> currentTextureSizes;
size_t currentVertexCount = 0;
glBindVertexArray(vertexArray);
auto finishCurrentBuffer = [&]() {
if (currentVertexCount > 0) {
GlVertexBuffer vb;
@ -947,10 +1006,12 @@ void OpenGlRenderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F co
for (auto const& vb : renderBuffer.vertexBuffers) {
glUniformMatrix3fv(m_vertexTransformUniform, 1, GL_TRUE, transformation.ptr());
for (size_t i = 0; i < vb.textures.size(); ++i) {
glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]);
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture);
if (m_currentEffect->includeVBTextures) {
for (size_t i = 0; i < vb.textures.size(); ++i) {
glUniform2f(m_textureSizeUniforms[i], vb.textures[i].size[0], vb.textures[i].size[1]);
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, vb.textures[i].texture);
}
}
for (auto const& p : m_currentEffect->textures) {
@ -977,7 +1038,7 @@ void OpenGlRenderer::renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F co
}
//Assumes the passed effect program is currently in use.
void OpenGlRenderer::setupGlUniforms(Effect& effect) {
void OpenGlRenderer::setupGlUniforms(Effect& effect, Vec2U screenSize) {
m_positionAttribute = effect.getAttribute("vertexPosition");
m_colorAttribute = effect.getAttribute("vertexColor");
m_texCoordAttribute = effect.getAttribute("vertexTextureCoordinate");
@ -985,17 +1046,21 @@ void OpenGlRenderer::setupGlUniforms(Effect& effect) {
m_textureUniforms.clear();
m_textureSizeUniforms.clear();
for (size_t i = 0; i < MultiTextureCount; ++i) {
m_textureUniforms.append(effect.getUniform(strf("texture{}", i).c_str()));
m_textureSizeUniforms.append(effect.getUniform(strf("textureSize{}", i).c_str()));
if (effect.includeVBTextures) {
for (size_t i = 0; i < MultiTextureCount; ++i) {
m_textureUniforms.append(effect.getUniform(strf("texture{}", i).c_str()));
m_textureSizeUniforms.append(effect.getUniform(strf("textureSize{}", i).c_str()));
}
}
m_screenSizeUniform = effect.getUniform("screenSize");
m_vertexTransformUniform = effect.getUniform("vertexTransform");
for (size_t i = 0; i < MultiTextureCount; ++i)
glUniform1i(m_textureUniforms[i], i);
if (effect.includeVBTextures) {
for (size_t i = 0; i < MultiTextureCount; ++i)
glUniform1i(m_textureUniforms[i], i);
}
glUniform2f(m_screenSizeUniform, m_screenSize[0], m_screenSize[1]);
glUniform2f(m_screenSizeUniform, screenSize[0], screenSize[1]);
}
RefPtr<OpenGlRenderer::GlFrameBuffer> OpenGlRenderer::getGlFrameBuffer(String const& id) {

View File

@ -143,6 +143,7 @@ private:
size_t vertexCount = 0;
};
GlRenderBuffer();
~GlRenderBuffer();
void set(List<RenderPrimitive>& primitives) override;
@ -152,6 +153,7 @@ private:
HashSet<TexturePtr> usedTextures;
List<GlVertexBuffer> vertexBuffers;
GLuint vertexArray = 0;
bool useMultiTexturing{true};
};
@ -178,6 +180,7 @@ private:
Json config;
bool blitted = false;
unsigned multisample = 0;
unsigned sizeDiv = 1;
GlFrameBuffer(Json const& config);
~GlFrameBuffer();
@ -195,6 +198,7 @@ private:
GLuint getAttribute(String const& name);
GLuint getUniform(String const& name);
bool includeVBTextures;
};
static bool logGlErrorSummary(String prefix);
@ -209,7 +213,7 @@ private:
void renderGlBuffer(GlRenderBuffer const& renderBuffer, Mat3F const& transformation);
void setupGlUniforms(Effect& effect);
void setupGlUniforms(Effect& effect, Vec2U screenSize);
RefPtr<OpenGlRenderer::GlFrameBuffer> getGlFrameBuffer(String const& id);
void blitGlFrameBuffer(RefPtr<OpenGlRenderer::GlFrameBuffer> const& frameBuffer);

View File

@ -104,6 +104,7 @@ Maybe<RectU> FramesSpecification::getRect(String const& frame) const {
Assets::Assets(Settings settings, StringList assetSources) {
const char* AssetsPatchSuffix = ".patch";
const char* AssetsPatchListSuffix = ".patchlist";
const char* AssetsLuaPatchSuffix = ".patch.lua";
m_settings = std::move(settings);
@ -123,6 +124,7 @@ Assets::Assets(Settings settings, StringList assetSources) {
LuaCallbacks callbacks;
callbacks.registerCallbackWithSignature<StringSet, String>("byExtension", bind(&Assets::scanExtension, this, _1));
callbacks.registerCallbackWithSignature<Json, String>("json", bind(&Assets::json, this, _1));
callbacks.registerCallbackWithSignature<bool, String>("exists", bind(&Assets::assetExists, this, _1));
callbacks.registerCallback("bytes", [this](String const& path) -> String {
auto assetBytes = bytes(path);
@ -204,6 +206,24 @@ Assets::Assets(Settings settings, StringList assetSources) {
auto targetPatchFile = filename.substr(0, filename.size() - strlen(AssetsLuaPatchSuffix));
if (auto p = m_files.ptr(targetPatchFile))
p->patchSources.append({filename, source});
} else if (filename.endsWith(AssetsPatchListSuffix, String::CaseInsensitive)) {
auto stream = source->read(filename);
size_t patchIndex = 0;
for (auto const& patchPair : inputUtf8Json(stream.begin(), stream.end(), JsonParseType::Top).iterateArray()) {
auto patches = patchPair.getArray("patches");
for (auto& path : patchPair.getArray("paths")) {
if (auto p = m_files.ptr(path.toString())) {
for (size_t i = 0; i != patches.size(); ++i) {
auto& patch = patches[i];
if (patch.isType(Json::Type::String))
p->patchSources.append({patch.toString(), source});
else
p->patchSources.append({strf("{}:[{}].patches[{}]", filename, patchIndex, i), source});
}
}
}
patchIndex++;
}
} else {
for (int i = 0; i < 10; i++) {
if (filename.endsWith(AssetsPatchSuffix + toString(i), String::CaseInsensitive)) {
@ -289,7 +309,7 @@ Assets::Assets(Settings settings, StringList assetSources) {
digest.push(assetPath);
digest.push(DataStreamBuffer::serialize(descriptor.source->open(descriptor.sourceName)->size()));
for (auto const& pair : descriptor.patchSources)
digest.push(DataStreamBuffer::serialize(pair.second->open(pair.first)->size()));
digest.push(DataStreamBuffer::serialize(pair.second->open(AssetPath::removeSubPath(pair.first))->size()));
}
}
@ -956,32 +976,35 @@ Json Assets::readJson(String const& path) const {
try {
Json result = inputUtf8Json(streamData.begin(), streamData.end(), JsonParseType::Top);
for (auto const& pair : m_files.get(path).patchSources) {
auto& patchPath = pair.first;
auto patchAssetPath = AssetPath::split(pair.first);
auto& patchBasePath = patchAssetPath.basePath;
auto& patchSource = pair.second;
auto patchStream = patchSource->read(patchPath);
if (patchPath.endsWith(".lua")) {
auto patchStream = patchSource->read(patchBasePath);
if (patchBasePath.endsWith(".lua")) {
RecursiveMutexLocker luaLocker(m_luaMutex);
// Kae: i don't like that lock. perhaps have a LuaEngine and patch context cache per worker thread later on?
LuaContextPtr& context = m_patchContexts[patchPath];
LuaContextPtr& context = m_patchContexts[patchBasePath];
if (!context) {
context = make_shared<LuaContext>(as<LuaEngine>(m_luaEngine.get())->createContext());
context->load(patchStream, patchPath);
context->load(patchStream, patchBasePath);
}
auto newResult = context->invokePath<Json>("patch", result, path);
if (newResult)
result = std::move(newResult);
} else {
auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), JsonParseType::Top);
if (patchAssetPath.subPath)
patchJson = patchJson.query(*patchAssetPath.subPath);
if (patchJson.isType(Json::Type::Array)) {
auto patchData = patchJson.toArray();
try {
result = checkPatchArray(patchPath, patchSource, result, patchData, {});
} catch (JsonPatchTestFail const& e) {
Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what());
} catch (JsonPatchException const& e) {
Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", patchPath, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what());
}
} else if (patchJson.isType(Json::Type::Object)) {//Kae: Do a good ol' json merge instead if the .patch file is a Json object
auto patchData = patchJson.toArray();
try {
result = checkPatchArray(pair.first, patchSource, result, patchData, {});
} catch (JsonPatchTestFail const& e) {
Logger::debug("Patch test failure from file {} in source: '{}' at '{}'. Caused by: {}", pair.first, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what());
} catch (JsonPatchException const& e) {
Logger::error("Could not apply patch from file {} in source: '{}' at '{}'. Caused by: {}", pair.first, patchSource->metadata().value("name", ""), m_assetSourcePaths.getLeft(patchSource), e.what());
}
} else if (patchJson.isType(Json::Type::Object)) {
result = jsonMergeNulling(result, patchJson.toObject());
}
}
@ -1219,7 +1242,7 @@ shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
shared_ptr<Assets::AssetData> Assets::loadAudio(AssetPath const& path) const {
return unlockDuring([&]() {
auto newData = make_shared<AudioData>();
newData->audio = make_shared<Audio>(open(path.basePath));
newData->audio = make_shared<Audio>(open(path.basePath), path.basePath);
newData->needsPostProcessing = newData->audio->compressed();
return newData;
});

View File

@ -64,12 +64,12 @@ void CellularLightArray<ScalarLightTraits>::calculatePointLighting(size_t xmin,
attenuation += min(blockAttenuation, circularizedPerBlockObstacleAttenuation) * m_pointObstacleBoost;
if (attenuation < 1.0f) {
auto newLight = ScalarLightTraits::subtract(light.value, attenuation);
if (ScalarLightTraits::maxIntensity(newLight) > 0.0001f) {
if (light.asSpread)
setLight(x, y, lvalue + newLight * 0.15f);
else
setLight(x, y, lvalue + newLight);
if (m_pointAdditive) {
auto newLight = ScalarLightTraits::subtract(light.value, attenuation);
if (ScalarLightTraits::maxIntensity(newLight) > 0.0001f)
setLight(x, y, lvalue + (light.asSpread ? newLight * 0.15f : newLight));
} else {
setLight(x, y, ScalarLightTraits::max(ScalarLightTraits::subtract(light.value, attenuation), lvalue));
}
}
}
@ -137,12 +137,12 @@ void CellularLightArray<ColoredLightTraits>::calculatePointLighting(size_t xmin,
attenuation += min(blockAttenuation, circularizedPerBlockObstacleAttenuation) * m_pointObstacleBoost;
if (attenuation < 1.0f) {
auto newLight = ColoredLightTraits::subtract(light.value, attenuation);
if (ColoredLightTraits::maxIntensity(newLight) > 0.0001f) {
if (light.asSpread)
setLight(x, y, lvalue + newLight * 0.15f);
else
setLight(x, y, lvalue + newLight);
if (m_pointAdditive) {
auto newLight = ColoredLightTraits::subtract(light.value, attenuation);
if (ColoredLightTraits::maxIntensity(newLight) > 0.0001f)
setLight(x, y, lvalue + (light.asSpread ? newLight * 0.15f : newLight));
} else {
setLight(x, y, ColoredLightTraits::max(ColoredLightTraits::subtract(light.value, attenuation), lvalue));
}
}
}

View File

@ -60,7 +60,7 @@ public:
};
void setParameters(unsigned spreadPasses, float spreadMaxAir, float spreadMaxObstacle,
float pointMaxAir, float pointMaxObstacle, float pointObstacleBoost);
float pointMaxAir, float pointMaxObstacle, float pointObstacleBoost, bool pointAdditive);
// The border around the target lighting array where initial lighting / light
// source data is required. Based on parameters.
@ -129,6 +129,7 @@ private:
float m_pointMaxAir;
float m_pointMaxObstacle;
float m_pointObstacleBoost;
bool m_pointAdditive;
};
typedef CellularLightArray<ColoredLightTraits> ColoredCellularLightArray;
@ -204,13 +205,14 @@ inline Vec3F ColoredLightTraits::max(Vec3F const& v1, Vec3F const& v2) {
template <typename LightTraits>
void CellularLightArray<LightTraits>::setParameters(unsigned spreadPasses, float spreadMaxAir, float spreadMaxObstacle,
float pointMaxAir, float pointMaxObstacle, float pointObstacleBoost) {
float pointMaxAir, float pointMaxObstacle, float pointObstacleBoost, bool pointAdditive) {
m_spreadPasses = spreadPasses;
m_spreadMaxAir = spreadMaxAir;
m_spreadMaxObstacle = spreadMaxObstacle;
m_pointMaxAir = pointMaxAir;
m_pointMaxObstacle = pointMaxObstacle;
m_pointObstacleBoost = pointObstacleBoost;
m_pointAdditive = pointAdditive;
}
template <typename LightTraits>

View File

@ -73,7 +73,8 @@ void CellularLightingCalculator::setParameters(Json const& config) {
config.getFloat("spreadMaxObstacle"),
config.getFloat("pointMaxAir"),
config.getFloat("pointMaxObstacle"),
config.getFloat("pointObstacleBoost")
config.getFloat("pointObstacleBoost"),
config.getBool("pointAdditive", false)
);
else
m_lightArray.left().setParameters(
@ -82,7 +83,8 @@ void CellularLightingCalculator::setParameters(Json const& config) {
config.getFloat("spreadMaxObstacle"),
config.getFloat("pointMaxAir"),
config.getFloat("pointMaxObstacle"),
config.getFloat("pointObstacleBoost")
config.getFloat("pointObstacleBoost"),
config.getBool("pointAdditive", false)
);
}
@ -190,7 +192,8 @@ void CellularLightIntensityCalculator::setParameters(Json const& config) {
config.getFloat("spreadMaxObstacle"),
config.getFloat("pointMaxAir"),
config.getFloat("pointMaxObstacle"),
config.getFloat("pointObstacleBoost")
config.getFloat("pointObstacleBoost"),
config.getBool("pointAdditive", false)
);
}

View File

@ -310,49 +310,54 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMi
m_mixBuffer[i] = 0;
ramt += silentSamples * channels;
}
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
while (ramt != bufferSize && !finished) {
// Only seek back to the beginning and read more data if loops is < 0
// (loop forever), or we have more loops to go, otherwise, the sample is
// finished.
if (audioInstance->m_loops != 0) {
audioInstance->m_audio.seekSample(0);
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
if (audioInstance->m_loops > 0)
--audioInstance->m_loops;
} else {
finished = true;
}
}
if (audioInstance->m_clockStop && *audioInstance->m_clockStop < sampleEndTime) {
for (size_t s = 0; s < ramt / channels; ++s) {
unsigned millisecondsInBuffer = (s * 1000) / sampleRate;
auto sampleTime = sampleStartTime + millisecondsInBuffer;
if (sampleTime > *audioInstance->m_clockStop) {
float volume = 0.0f;
if (audioInstance->m_clockStopFadeOut > 0)
volume = 1.0f - (float)(sampleTime - *audioInstance->m_clockStop) / (float)audioInstance->m_clockStopFadeOut;
if (volume <= 0) {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] = 0;
} else {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] *= volume;
}
try {
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
while (ramt != bufferSize && !finished) {
// Only seek back to the beginning and read more data if loops is < 0
// (loop forever), or we have more loops to go, otherwise, the sample is
// finished.
if (audioInstance->m_loops != 0) {
audioInstance->m_audio.seekSample(0);
ramt += audioInstance->m_audio.resample(channels, sampleRate, m_mixBuffer.ptr() + ramt, bufferSize - ramt, pitchMultiplier);
if (audioInstance->m_loops > 0)
--audioInstance->m_loops;
} else {
finished = true;
}
}
if (sampleEndTime > *audioInstance->m_clockStop + audioInstance->m_clockStopFadeOut)
finished = true;
}
if (audioInstance->m_clockStop && *audioInstance->m_clockStop < sampleEndTime) {
for (size_t s = 0; s < ramt / channels; ++s) {
unsigned millisecondsInBuffer = (s * 1000) / sampleRate;
auto sampleTime = sampleStartTime + millisecondsInBuffer;
if (sampleTime > *audioInstance->m_clockStop) {
float volume = 0.0f;
if (audioInstance->m_clockStopFadeOut > 0)
volume = 1.0f - (float)(sampleTime - *audioInstance->m_clockStop) / (float)audioInstance->m_clockStopFadeOut;
for (size_t s = 0; s < ramt / channels; ++s) {
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
for (size_t c = 0; c < channels; ++c) {
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
int16_t& outSample = outBuffer[s * channels + c];
outSample = clamp(sample + outSample, -32767.0f, 32767.0f);
if (volume <= 0) {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] = 0;
} else {
for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] *= volume;
}
}
}
if (sampleEndTime > *audioInstance->m_clockStop + audioInstance->m_clockStopFadeOut)
finished = true;
}
for (size_t s = 0; s < ramt / channels; ++s) {
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
for (size_t c = 0; c < channels; ++c) {
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
int16_t& outSample = outBuffer[s * channels + c];
outSample = clamp(sample + outSample, -32767.0f, 32767.0f);
}
}
} catch (Star::AudioException const& e) {
Logger::error("Error reading audio '{}': {}", audioInstance->m_audio.name(), e.what());
finished = true;
}
audioInstance->m_volume.value = audioStopVolEnd;

View File

@ -301,20 +301,19 @@ void ClientApplication::processInput(InputEvent const& event) {
m_controllerRightStick[1] = cAxis->controllerAxisValue;
}
if (!m_errorScreen->accepted() && m_errorScreen->handleInputEvent(event))
return;
bool processed = !m_errorScreen->accepted() && m_errorScreen->handleInputEvent(event);
bool processed = false;
if (!processed) {
if (m_state == MainAppState::Splash) {
processed = m_cinematicOverlay->handleInputEvent(event);
} else if (m_state == MainAppState::Title) {
if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
processed = m_titleScreen->handleInputEvent(event);
if (m_state == MainAppState::Splash) {
processed = m_cinematicOverlay->handleInputEvent(event);
} else if (m_state == MainAppState::Title) {
if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
processed = m_titleScreen->handleInputEvent(event);
} else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) {
if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
processed = m_mainInterface->handleInputEvent(event);
} else if (m_state == MainAppState::SinglePlayer || m_state == MainAppState::MultiPlayer) {
if (!(processed = m_cinematicOverlay->handleInputEvent(event)))
processed = m_mainInterface->handleInputEvent(event);
}
}
m_input->handleInput(event, processed);
@ -361,7 +360,7 @@ void ClientApplication::update() {
m_guiContext->cleanup();
m_edgeKeyEvents.clear();
m_input->reset();
m_input->update();
}
void ClientApplication::render() {
@ -401,6 +400,17 @@ void ClientApplication::render() {
});
LogMap::set("client_render_world_painter", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - paintStart));
LogMap::set("client_render_world_total", strf(u8"{:05d}\u00b5s", Time::monotonicMicroseconds() - totalStart));
auto size = Vec2F(renderer->screenSize());
auto quad = renderFlatRect(RectF::withSize(size / -2, size), Vec4B::filled(0), 0.0f);
for (auto& layer : m_postProcessLayers) {
for (unsigned i = 0; i < layer.passes; i++) {
for (auto& effect : layer.effects) {
renderer->switchEffectConfig(effect);
renderer->render(quad);
}
}
}
}
renderer->switchEffectConfig("interface");
auto start = Time::monotonicMicroseconds();
@ -450,8 +460,18 @@ void ClientApplication::renderReload() {
};
renderer->loadConfig(assets->json("/rendering/opengl.config"));
loadEffectConfig("world");
m_postProcessLayers.clear();
auto postProcessLayers = assets->json("/client.config:postProcessLayers").toArray();
for (auto& layer : postProcessLayers) {
auto effects = jsonToStringList(layer.getArray("effects"));
for (auto& effect : effects)
loadEffectConfig(effect);
m_postProcessLayers.append(PostProcessLayer{ std::move(effects), (unsigned)layer.getUInt("passes", 1) });
}
loadEffectConfig("interface");
}
@ -585,6 +605,7 @@ void ClientApplication::changeState(MainAppState newState) {
m_mainMixer->setUniverseClient(m_universeClient);
m_universeClient->setMainPlayer(m_player);
m_cinematicOverlay->setPlayer(m_player);
m_timeSinceJoin = (int64_t)Time::millisecondsSinceEpoch() / 1000;
auto assets = m_root->assets();
String loadingCinematic = assets->json("/client.config:loadingCinematic").toString();
@ -686,10 +707,11 @@ void ClientApplication::setError(String const& error, std::exception const& e) {
void ClientApplication::updateMods(float dt) {
m_cinematicOverlay->update(dt);
auto ugcService = appController()->userGeneratedContentService();
if (ugcService) {
if (ugcService && m_root->settings().includeUGC) {
Logger::info("Checking for user generated content...");
if (ugcService->triggerContentDownload()) {
StringList modDirectories;
for (auto contentId : ugcService->subscribedContentIds()) {
for (auto& contentId : ugcService->subscribedContentIds()) {
if (auto contentDirectory = ugcService->contentDownloadDirectory(contentId)) {
Logger::info("Loading mods from user generated content with id '{}' from directory '{}'", contentId, *contentDirectory);
modDirectories.append(*contentDirectory);
@ -748,8 +770,36 @@ void ClientApplication::updateTitle(float dt) {
appController()->setAcceptingTextInput(m_titleScreen->textInputActive());
auto p2pNetworkingService = appController()->p2pNetworkingService();
if (p2pNetworkingService)
p2pNetworkingService->setActivityData("In Main Menu", {});
if (p2pNetworkingService) {
auto getStateString = [](TitleState state) -> const char* {
switch (state) {
case TitleState::Main:
return "In Main Menu";
case TitleState::Options:
return "In Options";
case TitleState::Mods:
return "In Mods";
case TitleState::SinglePlayerSelectCharacter:
return "Selecting a character for singleplayer";
case TitleState::SinglePlayerCreateCharacter:
return "Creating a character for singleplayer";
case TitleState::MultiPlayerSelectCharacter:
return "Selecting a character for multiplayer";
case TitleState::MultiPlayerCreateCharacter:
return "Creating a character for multiplayer";
case TitleState::MultiPlayerConnect:
return "Awaiting multiplayer connection info";
case TitleState::StartSinglePlayer:
return "Loading Singleplayer";
case TitleState::StartMultiPlayer:
return "Connecting to Multiplayer";
default:
return "";
}
};
p2pNetworkingService->setActivityData("Not In Game", getStateString(m_titleScreen->currentState()), 0, {});
}
if (m_titleScreen->currentState() == TitleState::StartSinglePlayer) {
changeState(MainAppState::SinglePlayer);
@ -791,6 +841,7 @@ void ClientApplication::updateTitle(float dt) {
void ClientApplication::updateRunning(float dt) {
try {
auto worldClient = m_universeClient->worldClient();
auto p2pNetworkingService = appController()->p2pNetworkingService();
bool clientIPJoinable = m_root->configuration()->get("clientIPJoinable").toBool();
bool clientP2PJoinable = m_root->configuration()->get("clientP2PJoinable").toBool();
@ -817,8 +868,54 @@ void ClientApplication::updateRunning(float dt) {
}
}
if (p2pNetworkingService)
p2pNetworkingService->setActivityData("In Game", party);
if (p2pNetworkingService) {
auto getActivityDetail = [&](String const& tag) -> String {
if (tag == "playerName")
return Text::stripEscapeCodes(m_player->name());
if (tag == "playerHealth")
return toString(m_player->health());
if (tag == "playerMaxHealth")
return toString(m_player->maxHealth());
if (tag == "playerEnergy")
return toString(m_player->energy());
if (tag == "playerMaxEnergy")
return toString(m_player->maxEnergy());
if (tag == "playerBreath")
return toString(m_player->breath());
if (tag == "playerMaxBreath")
return toString(m_player->maxBreath());
if (tag == "playerXPos")
return toString(round(m_player->position().x()));
if (tag == "playerYPos")
return toString(round(m_player->position().y()));
if (tag == "worldName") {
if (m_universeClient->clientContext()->playerWorldId().is<ClientShipWorldId>())
return "Player Ship";
else if (WorldTemplate const* worldTemplate = worldClient ? worldClient->currentTemplate().get() : nullptr) {
auto worldName = worldTemplate->worldName();
if (worldName.empty())
return "In World";
else
return Text::stripEscapeCodes(worldName);
}
else
return "Nowhere";
}
return "";
};
String finalDetails = "";
Json activityDetails = m_root->configuration()->getPath("discord.activityDetails");
if (activityDetails.isType(Json::Type::Array)) {
StringList detailsList;
for (auto& detail : activityDetails.iterateArray())
detailsList.append(getActivityDetail(*detail.stringPtr()));
finalDetails = detailsList.join("\n");
} else if (activityDetails.isType(Json::Type::String))
finalDetails = activityDetails.toString().lookupTags(getActivityDetail);
p2pNetworkingService->setActivityData("In Game", finalDetails.utf8Ptr(), m_timeSinceJoin, party);
}
if (!m_mainInterface->inputFocus() && !m_cinematicOverlay->suppressInput()) {
m_player->setShifting(isActionTaken(InterfaceAction::PlayerShifting));
@ -896,7 +993,7 @@ void ClientApplication::updateRunning(float dt) {
config->set("zoomLevel", newZoom);
}
if (m_controllerLeftStick.magnitudeSquared() > 0.001f)
if (m_controllerLeftStick.magnitudeSquared() > 0.01f)
m_player->setMoveVector(m_controllerLeftStick);
else
m_player->setMoveVector(Vec2F());
@ -932,7 +1029,7 @@ void ClientApplication::updateRunning(float dt) {
if (checkDisconnection())
return;
if (auto worldClient = m_universeClient->worldClient()) {
if (worldClient) {
m_worldPainter->update(dt);
auto& broadcastCallback = worldClient->broadcastCallback();
if (!broadcastCallback) {

View File

@ -52,6 +52,11 @@ private:
String account;
String password;
};
struct PostProcessLayer {
List<String> effects;
unsigned passes;
};
void renderReload();
@ -98,6 +103,8 @@ private:
WorldPainterPtr m_worldPainter;
WorldRenderData m_renderData;
MainInterfacePtr m_mainInterface;
List<PostProcessLayer> m_postProcessLayers;
// Valid if main app state == SinglePlayer
UniverseServerPtr m_universeServer;
@ -108,7 +115,7 @@ private:
int m_cameraOffsetDownTicks = 0;
Vec2F m_cameraPositionSmoother;
Vec2F m_cameraSmoothDelta;
int m_cameraZoomDirection;
int m_cameraZoomDirection = 0;
int m_minInterfaceScale = 2;
int m_maxInterfaceScale = 3;
@ -121,6 +128,7 @@ private:
Maybe<PendingMultiPlayerConnection> m_pendingMultiPlayerConnection;
Maybe<HostAddressWithPort> m_currentRemoteJoin;
int64_t m_timeSinceJoin = 0;
};
}

View File

@ -239,17 +239,19 @@ public:
size_t readPartial(int16_t* buffer, size_t bufferSize) {
int bitstream;
int read;
int read = OV_HOLE;
// ov_read takes int parameter, so do some magic here to make sure we don't
// overflow
bufferSize *= 2;
do {
#if STAR_LITTLE_ENDIAN
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 0, 2, 1, &bitstream);
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 0, 2, 1, &bitstream);
#else
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 1, 2, 1, &bitstream);
read = ov_read(&m_vorbisFile, (char*)buffer, bufferSize, 1, 2, 1, &bitstream);
#endif
} while (read == OV_HOLE);
if (read < 0)
throw AudioException("Error in Audio::read");
throw AudioException::format("Error in Audio::read ({})", read);
// read in bytes, returning number of int16_t samples.
return read / 2;
@ -349,7 +351,8 @@ private:
ExternalBuffer m_memoryFile;
};
Audio::Audio(IODevicePtr device) {
Audio::Audio(IODevicePtr device, String name) {
m_name = name;
if (!device->isOpen())
device->open(IOMode::Read);
@ -579,4 +582,12 @@ size_t Audio::resample(unsigned destinationChannels, unsigned destinationSampleR
}
}
String const& Audio::name() const {
return m_name;
}
void Audio::setName(String name) {
m_name = std::move(name);
}
}

View File

@ -26,7 +26,7 @@ STAR_EXCEPTION(AudioException, StarException);
// instances is not expensive.
class Audio {
public:
explicit Audio(IODevicePtr device);
explicit Audio(IODevicePtr device, String name = "");
Audio(Audio const& audio);
Audio(Audio&& audio);
@ -90,12 +90,16 @@ public:
int16_t* destinationBuffer, size_t destinationBufferSize,
double velocity = 1.0);
String const& name() const;
void setName(String name);
private:
// If audio is uncompressed, this will be null.
CompressedAudioImplPtr m_compressed;
UncompressedAudioImplPtr m_uncompressed;
ByteArray m_workingBuffer;
String m_name;
};
}

View File

@ -196,6 +196,11 @@ void BTreeDatabase::forAll(function<void(ByteArray, ByteArray)> v) {
m_impl.forAll(std::move(v));
}
void BTreeDatabase::recoverAll(function<void(ByteArray, ByteArray)> v, function<void(String const&, std::exception const&)> e) {
ReadLocker readLocker(m_lock);
m_impl.recoverAll(std::move(v), std::move(e));
}
bool BTreeDatabase::insert(ByteArray const& k, ByteArray const& data) {
WriteLocker writeLocker(m_lock);
checkKeySize(k);

View File

@ -64,6 +64,7 @@ public:
void forEach(ByteArray const& lower, ByteArray const& upper, function<void(ByteArray, ByteArray)> v);
void forAll(function<void(ByteArray, ByteArray)> v);
void recoverAll(function<void(ByteArray, ByteArray)> v, function<void(String const&, std::exception const&)> e);
// Returns true if a value was overwritten
bool insert(ByteArray const& k, ByteArray const& data);

View File

@ -10,6 +10,8 @@ namespace Star {
struct WindowsSymInitializer {
WindowsSymInitializer() {
DWORD options = SymGetOptions();
SymSetOptions(options | SYMOPT_LOAD_LINES);
if (!SymInitialize(GetCurrentProcess(), NULL, TRUE))
fatalError("SymInitialize failed", false);
}
@ -126,9 +128,19 @@ OutputProxy outputStack(StackCapture stack) {
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
DWORD64 displacement = 0;
format(os, "[{}] {}", i, (void*)stack.first[i]);
if (SymFromAddr(process, stack.first[i], &displacement, symbol))
format(os, "[{:0{}}] {}", i, (int)log10(stack.second) + 1, (void*)stack.first[i]);
IMAGEHLP_LINE64 line{};
DWORD displacement = 0;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(process, stack.first[i], &displacement, &line) && *line.FileName) {
char* file = line.FileName;
for (char* i = file; *i; ++i) {
if (*i == '\\' || *i == '/')
file = i;
}
format(os, " ({}:{})", ++file, line.LineNumber);
}
if (SymFromAddr(process, stack.first[i], NULL, symbol))
format(os, " {}", symbol->Name);
if (i + 1 < stack.second)

View File

@ -73,23 +73,23 @@ EnumMap<Key> const KeyNames{
{Key::Y, "Y"},
{Key::Z, "Z"},
{Key::Delete, "Del"},
{Key::Kp0, "Kp0"},
{Key::Kp1, "Kp1"},
{Key::Kp2, "Kp2"},
{Key::Kp3, "Kp3"},
{Key::Kp4, "Kp4"},
{Key::Kp5, "Kp5"},
{Key::Kp6, "Kp6"},
{Key::Kp7, "Kp7"},
{Key::Kp8, "Kp8"},
{Key::Kp9, "Kp9"},
{Key::Kp_period, "Kp_period"},
{Key::Kp_divide, "Kp_divide"},
{Key::Kp_multiply, "Kp_multiply"},
{Key::Kp_minus, "Kp_minus"},
{Key::Kp_plus, "Kp_plus"},
{Key::Kp_enter, "Kp_enter"},
{Key::Kp_equals, "Kp_equals"},
{Key::Keypad0, "Keypad 0"},
{Key::Keypad1, "Keypad 1"},
{Key::Keypad2, "Keypad 2"},
{Key::Keypad3, "Keypad 3"},
{Key::Keypad4, "Keypad 4"},
{Key::Keypad5, "Keypad 5"},
{Key::Keypad6, "Keypad 6"},
{Key::Keypad7, "Keypad 7"},
{Key::Keypad8, "Keypad 8"},
{Key::Keypad9, "Keypad 9"},
{Key::KeypadPeriod, "Keypad ."},
{Key::KeypadDivide, "Keypad /"},
{Key::KeypadMultiply, "Keypad *"},
{Key::KeypadMinus, "Keypad -"},
{Key::KeypadPlus, "Keypad +"},
{Key::KeypadEnter, "Keypad Enter"},
{Key::KeypadEquals, "Keypad ="},
{Key::Up, "Up"},
{Key::Down, "Down"},
{Key::Right, "Right"},
@ -114,6 +114,15 @@ EnumMap<Key> const KeyNames{
{Key::F13, "F13"},
{Key::F14, "F14"},
{Key::F15, "F15"},
{Key::F16, "F16"},
{Key::F17, "F17"},
{Key::F18, "F18"},
{Key::F19, "F19"},
{Key::F20, "F20"},
{Key::F21, "F21"},
{Key::F22, "F22"},
{Key::F23, "F23"},
{Key::F24, "F24"},
{Key::NumLock, "NumLock"},
{Key::CapsLock, "CapsLock"},
{Key::ScrollLock, "ScrollLock"},

View File

@ -78,23 +78,23 @@ enum class Key : uint16_t {
Y,
Z,
Delete,
Kp0,
Kp1,
Kp2,
Kp3,
Kp4,
Kp5,
Kp6,
Kp7,
Kp8,
Kp9,
Kp_period,
Kp_divide,
Kp_multiply,
Kp_minus,
Kp_plus,
Kp_enter,
Kp_equals,
Keypad0,
Keypad1,
Keypad2,
Keypad3,
Keypad4,
Keypad5,
Keypad6,
Keypad7,
Keypad8,
Keypad9,
KeypadPeriod,
KeypadDivide,
KeypadMultiply,
KeypadMinus,
KeypadPlus,
KeypadEnter,
KeypadEquals,
Up,
Down,
Right,
@ -137,7 +137,18 @@ enum class Key : uint16_t {
SysReq,
Pause,
Menu,
Power
Power,
// can't have this where I want because canvases
// pass keycodes to Lua as a numeric code >:[
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24
};
extern EnumMap<Key> const KeyNames;

View File

@ -89,7 +89,7 @@ void Logger::refreshLoggable() {
shared_ptr<StdoutLogSink> Logger::s_stdoutSink = make_shared<StdoutLogSink>();
HashSet<LogSinkPtr> Logger::s_sinks{s_stdoutSink};
Array<bool, 4> Logger::s_loggable = Array<bool, 4>::filled(false);
Array<bool, 4> Logger::s_loggable = Array<bool, 4>{false, true, true, true};
Mutex Logger::s_mutex;
String LogMap::getValue(String const& key) {

View File

@ -2,6 +2,18 @@
#ifdef STAR_USE_JEMALLOC
#include "jemalloc/jemalloc.h"
#elif STAR_USE_MIMALLOC
#include "mimalloc.h"
#elif STAR_USE_RPMALLOC
#include "rpnew.h"
bool rpm_linker_ref() {
rpmalloc_linker_reference();
return true;
}
static bool _rpm_linker_ref = rpm_linker_ref();
#endif
namespace Star {
@ -42,6 +54,38 @@ namespace Star {
::sdallocx(ptr, size, 0);
}
#endif
#elif STAR_USE_MIMALLOC
void* malloc(size_t size) {
return mi_malloc(size);
}
void* realloc(void* ptr, size_t size) {
return mi_realloc(ptr, size);
}
void free(void* ptr) {
return mi_free(ptr);
}
void free(void* ptr, size_t size) {
return mi_free_size(ptr, size);
}
#elif STAR_USE_RPMALLOC
void* malloc(size_t size) {
return rpmalloc(size);
}
void* realloc(void* ptr, size_t size) {
return rprealloc(ptr, size);
}
void free(void* ptr) {
return rpfree(ptr);
}
void free(void* ptr, size_t) {
return rpfree(ptr);
}
#else
void* malloc(size_t size) {
return ::malloc(size);
@ -62,6 +106,9 @@ namespace Star {
}
#ifndef STAR_USE_RPMALLOC
void* operator new(std::size_t size) {
auto ptr = Star::malloc(size);
if (!ptr)
@ -110,3 +157,5 @@ void operator delete(void* ptr, std::size_t size) noexcept {
void operator delete[](void* ptr, std::size_t size) noexcept {
Star::free(ptr, size);
}
#endif

View File

@ -4,6 +4,8 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include "StarString_windows.hpp"
#else
#ifdef STAR_SYSTEM_FREEBSD
#include <sys/types.h>
@ -42,17 +44,19 @@ static WindowsSocketInitializer g_windowsSocketInitializer;
inline String netErrorString() {
#ifdef STAR_SYSTEM_WINDOWS
LPVOID lpMsgBuf = NULL;
LPWSTR lpMsgBuf = NULL;
int error = WSAGetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
NULL,
WSAGetLastError(),
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL);
String result = String((char*)lpMsgBuf);
String result = strf("{} - {}", error, utf16ToString(lpMsgBuf));
if (lpMsgBuf != NULL)
LocalFree(lpMsgBuf);

View File

@ -208,8 +208,8 @@ typename Container::value_type Random::randValueFrom(
template <typename Container>
void Random::shuffle(Container& container) {
size_t max = container.size();
std::shuffle(container.begin(), container.end(), URBG<size_t>([max]() { return Random::randUInt(max - 1); }));
RandomSource random;
std::shuffle(container.begin(), container.end(), URBG<size_t>([&]() { return static_cast<size_t>(random.randu64()); }));
}
}

View File

@ -134,11 +134,14 @@ private:
template <typename Container, typename T, typename... TL>
void staticRandomShuffle(Container& container, T const& d, TL const&... rest) {
int mix = 0;
size_t max = container.size();
std::shuffle(container.begin(), container.end(), URBG<size_t>([&]() {
return staticRandomU32Range(0, max - 1, ++mix, d, rest...);
}));
auto begin = container.begin();
auto end = container.end();
auto it = begin;
for (int i = 1, mix = 0; ++it != end; ++i) {
int off = staticRandomU32Range(0, i, ++mix, d, rest...);
if (off != i)
std::swap(*it, *(begin + off));
}
}
}

View File

@ -36,6 +36,8 @@ namespace Text {
static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc));
String stripEscapeCodes(String const& s) {
if (s.empty())
return s;
return std::regex_replace(s.utf8(), stripEscapeRegex, "");
}

View File

@ -131,7 +131,18 @@ struct MutexImpl {
}
void lock() {
pthread_mutex_lock(&mutex);
#ifndef STAR_SYSTEM_MACOS
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 60;
if (pthread_mutex_timedlock(&mutex, &ts) != 0) {
Logger::warn("Mutex lock is taking too long, printing stack");
printStack("Mutex::lock");
#else
{
#endif
pthread_mutex_lock(&mutex);
}
}
void unlock() {
@ -199,7 +210,18 @@ struct RecursiveMutexImpl {
}
void lock() {
pthread_mutex_lock(&mutex);
#ifndef STAR_SYSTEM_MACOS
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 60;
if (pthread_mutex_timedlock(&mutex, &ts) != 0) {
Logger::warn("RecursiveMutex lock is taking too long, printing stack");
printStack("RecursiveMutex::lock");
#else
{
#endif
pthread_mutex_lock(&mutex);
}
}
void unlock() {

View File

@ -70,4 +70,15 @@ SET (star_extern_SOURCES
lua/lzio.c
)
IF (STAR_USE_RPMALLOC)
SET(star_extern_HEADERS ${star_extern_HEADERS}
rpmalloc.h
rpnew.h
)
SET(star_extern_SOURCES ${star_extern_SOURCES}
rpmalloc.c
)
ENDIF()
ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS})

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@
# include <locale>
#endif
#ifdef _WIN32
#if defined(_WIN32) && !defined(FMT_WINDOWS_NO_WCHAR)
# include <io.h> // _isatty
#endif
@ -58,8 +58,8 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
auto it = buffer_appender<char>(out);
if (message.size() <= inline_buffer_size - error_code_size)
format_to(it, FMT_STRING("{}{}"), message, SEP);
format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
fmt::format_to(it, FMT_STRING("{}{}"), message, SEP);
fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
FMT_ASSERT(out.size() <= inline_buffer_size, "");
}
@ -73,9 +73,8 @@ FMT_FUNC void report_error(format_func func, int error_code,
}
// A wrapper around fwrite that throws on error.
inline void fwrite_fully(const void* ptr, size_t size, size_t count,
FILE* stream) {
size_t written = std::fwrite(ptr, size, count, stream);
inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) {
size_t written = std::fwrite(ptr, 1, count, stream);
if (written < count)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
@ -86,7 +85,7 @@ locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
}
template <typename Locale> Locale locale_ref::get() const {
template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, std::locale>::value, "");
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
}
@ -98,7 +97,8 @@ FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
return {std::move(grouping), thousands_sep};
}
template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
template <typename Char>
FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
.decimal_point();
}
@ -144,24 +144,25 @@ FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
}
#endif
FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
format_args args) {
FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args)
-> std::system_error {
auto ec = std::error_code(error_code, std::generic_category());
return std::system_error(ec, vformat(fmt, args));
}
namespace detail {
template <typename F> inline bool operator==(basic_fp<F> x, basic_fp<F> y) {
template <typename F>
inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
return x.f == y.f && x.e == y.e;
}
// Compilers should be able to optimize this into the ror instruction.
FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept {
FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
r &= 31;
return (n >> r) | (n << (32 - r));
}
FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
r &= 63;
return (n >> r) | (n << (64 - r));
}
@ -170,14 +171,14 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
namespace dragonbox {
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
// 64-bit unsigned integer.
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t {
return umul128_upper64(static_cast<uint64_t>(x) << 32, y);
}
// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a
// 128-bit unsigned integer.
inline uint128_fallback umul192_lower128(uint64_t x,
uint128_fallback y) noexcept {
inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept
-> uint128_fallback {
uint64_t high = x * y.high();
uint128_fallback high_low = umul128(x, y.low());
return {high + high_low.high(), high_low.low()};
@ -185,12 +186,12 @@ inline uint128_fallback umul192_lower128(uint64_t x,
// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a
// 64-bit unsigned integer.
inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t {
return x * y;
}
// Various fast log computations.
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
return (e * 631305 - 261663) >> 21;
}
@ -204,7 +205,7 @@ FMT_INLINE_VARIABLE constexpr struct {
// divisible by pow(10, N).
// Precondition: n <= pow(10, N + 1).
template <int N>
bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept {
auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool {
// The numbers below are chosen such that:
// 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100,
// 2. nm mod 2^k < m if and only if n is divisible by d,
@ -229,7 +230,7 @@ bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept {
// Computes floor(n / pow(10, N)) for small n and N.
// Precondition: n <= pow(10, N + 1).
template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept {
template <int N> auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t {
constexpr auto info = div_small_pow10_infos[N - 1];
FMT_ASSERT(n <= info.divisor * 10, "n is too large");
constexpr uint32_t magic_number =
@ -238,12 +239,12 @@ template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept {
}
// Computes floor(n / 10^(kappa + 1)) (float)
inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept {
inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t {
// 1374389535 = ceil(2^37/100)
return static_cast<uint32_t>((static_cast<uint64_t>(n) * 1374389535) >> 37);
}
// Computes floor(n / 10^(kappa + 1)) (double)
inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t {
// 2361183241434822607 = ceil(2^(64+7)/1000)
return umul128_upper64(n, 2361183241434822607ull) >> 7;
}
@ -255,7 +256,7 @@ template <> struct cache_accessor<float> {
using carrier_uint = float_info<float>::carrier_uint;
using cache_entry_type = uint64_t;
static uint64_t get_cached_power(int k) noexcept {
static auto get_cached_power(int k) noexcept -> uint64_t {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range");
static constexpr const uint64_t pow10_significands[] = {
@ -297,20 +298,23 @@ template <> struct cache_accessor<float> {
bool is_integer;
};
static compute_mul_result compute_mul(
carrier_uint u, const cache_entry_type& cache) noexcept {
static auto compute_mul(carrier_uint u,
const cache_entry_type& cache) noexcept
-> compute_mul_result {
auto r = umul96_upper64(u, cache);
return {static_cast<carrier_uint>(r >> 32),
static_cast<carrier_uint>(r) == 0};
}
static uint32_t compute_delta(const cache_entry_type& cache,
int beta) noexcept {
static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
-> uint32_t {
return static_cast<uint32_t>(cache >> (64 - 1 - beta));
}
static compute_mul_parity_result compute_mul_parity(
carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept {
static auto compute_mul_parity(carrier_uint two_f,
const cache_entry_type& cache,
int beta) noexcept
-> compute_mul_parity_result {
FMT_ASSERT(beta >= 1, "");
FMT_ASSERT(beta < 64, "");
@ -319,22 +323,22 @@ template <> struct cache_accessor<float> {
static_cast<uint32_t>(r >> (32 - beta)) == 0};
}
static carrier_uint compute_left_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept {
static auto compute_left_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
return static_cast<carrier_uint>(
(cache - (cache >> (num_significand_bits<float>() + 2))) >>
(64 - num_significand_bits<float>() - 1 - beta));
}
static carrier_uint compute_right_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept {
static auto compute_right_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
return static_cast<carrier_uint>(
(cache + (cache >> (num_significand_bits<float>() + 1))) >>
(64 - num_significand_bits<float>() - 1 - beta));
}
static carrier_uint compute_round_up_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept {
static auto compute_round_up_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
return (static_cast<carrier_uint>(
cache >> (64 - num_significand_bits<float>() - 2 - beta)) +
1) /
@ -346,7 +350,7 @@ template <> struct cache_accessor<double> {
using carrier_uint = float_info<double>::carrier_uint;
using cache_entry_type = uint128_fallback;
static uint128_fallback get_cached_power(int k) noexcept {
static auto get_cached_power(int k) noexcept -> uint128_fallback {
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
"k is out of range");
@ -985,8 +989,7 @@ template <> struct cache_accessor<double> {
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
{ 0xdb68c2ca82ed2a05,
0xa67398db9f6820e2 }
{0xdb68c2ca82ed2a05, 0xa67398db9f6820e2},
#else
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
@ -1071,19 +1074,22 @@ template <> struct cache_accessor<double> {
bool is_integer;
};
static compute_mul_result compute_mul(
carrier_uint u, const cache_entry_type& cache) noexcept {
static auto compute_mul(carrier_uint u,
const cache_entry_type& cache) noexcept
-> compute_mul_result {
auto r = umul192_upper128(u, cache);
return {r.high(), r.low() == 0};
}
static uint32_t compute_delta(cache_entry_type const& cache,
int beta) noexcept {
static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
-> uint32_t {
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
}
static compute_mul_parity_result compute_mul_parity(
carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept {
static auto compute_mul_parity(carrier_uint two_f,
const cache_entry_type& cache,
int beta) noexcept
-> compute_mul_parity_result {
FMT_ASSERT(beta >= 1, "");
FMT_ASSERT(beta < 64, "");
@ -1092,35 +1098,35 @@ template <> struct cache_accessor<double> {
((r.high() << beta) | (r.low() >> (64 - beta))) == 0};
}
static carrier_uint compute_left_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept {
static auto compute_left_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
return (cache.high() -
(cache.high() >> (num_significand_bits<double>() + 2))) >>
(64 - num_significand_bits<double>() - 1 - beta);
}
static carrier_uint compute_right_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept {
static auto compute_right_endpoint_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
return (cache.high() +
(cache.high() >> (num_significand_bits<double>() + 1))) >>
(64 - num_significand_bits<double>() - 1 - beta);
}
static carrier_uint compute_round_up_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept {
static auto compute_round_up_for_shorter_interval_case(
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
return ((cache.high() >> (64 - num_significand_bits<double>() - 2 - beta)) +
1) /
2;
}
};
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback {
return cache_accessor<double>::get_cached_power(k);
}
// Various integer checks
template <typename T>
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
return exponent >= case_shorter_interval_left_endpoint_lower_threshold &&
@ -1128,16 +1134,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
}
// Remove trailing zeros from n and return the number of zeros removed (float)
FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
// See https://github.com/fmtlib/fmt/issues/3163 for more details.
const uint32_t mod_inv_5 = 0xcccccccd;
// Casts are needed to workaround a bug in MSVC 19.22 and older.
const uint32_t mod_inv_25 =
static_cast<uint32_t>(uint64_t(mod_inv_5) * mod_inv_5);
constexpr uint32_t mod_inv_5 = 0xcccccccd;
constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5
int s = 0;
while (true) {
auto q = rotr(n * mod_inv_25, 2);
if (q > max_value<uint32_t>() / 100) break;
@ -1162,32 +1164,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// Is n is divisible by 10^8?
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
// If yes, work with the quotient.
// If yes, work with the quotient...
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
const uint32_t mod_inv_5 = 0xcccccccd;
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
int s = 8;
while (true) {
auto q = rotr(n32 * mod_inv_25, 2);
if (q > max_value<uint32_t>() / 100) break;
n32 = q;
s += 2;
}
auto q = rotr(n32 * mod_inv_5, 1);
if (q <= max_value<uint32_t>() / 10) {
n32 = q;
s |= 1;
}
// ... and use the 32 bit variant of the function
int s = remove_trailing_zeros(n32, 8);
n = n32;
return s;
}
// If n is not divisible by 10^8, work with n itself.
const uint64_t mod_inv_5 = 0xcccccccccccccccd;
const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5;
constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;
constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5
int s = 0;
while (true) {
@ -1253,7 +1240,7 @@ FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
return ret_value;
}
template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
template <typename T> auto to_decimal(T x) noexcept -> decimal_fp<T> {
// Step 1: integer promotion & Schubfach multiplier calculation.
using carrier_uint = typename float_info<T>::carrier_uint;
@ -1392,15 +1379,15 @@ template <> struct formatter<detail::bigint> {
for (auto i = n.bigits_.size(); i > 0; --i) {
auto value = n.bigits_[i - 1u];
if (first) {
out = format_to(out, FMT_STRING("{:x}"), value);
out = fmt::format_to(out, FMT_STRING("{:x}"), value);
first = false;
continue;
}
out = format_to(out, FMT_STRING("{:08x}"), value);
out = fmt::format_to(out, FMT_STRING("{:08x}"), value);
}
if (n.exp_ > 0)
out = format_to(out, FMT_STRING("p{}"),
n.exp_ * detail::bigint::bigit_bits);
out = fmt::format_to(out, FMT_STRING("p{}"),
n.exp_ * detail::bigint::bigit_bits);
return out;
}
};
@ -1436,7 +1423,7 @@ FMT_FUNC void report_system_error(int error_code,
report_error(format_system_error, error_code, message);
}
FMT_FUNC std::string vformat(string_view fmt, format_args args) {
FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
// Don't optimize the "{}" case to keep the binary size small and because it
// can be better optimized in fmt::format anyway.
auto buffer = memory_buffer();
@ -1445,33 +1432,43 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
}
namespace detail {
#ifndef _WIN32
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
#if !defined(_WIN32) || defined(FMT_WINDOWS_NO_WCHAR)
FMT_FUNC auto write_console(int, string_view) -> bool { return false; }
FMT_FUNC auto write_console(std::FILE*, string_view) -> bool { return false; }
#else
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
void*, const void*, dword, dword*, void*);
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
auto fd = _fileno(f);
if (!_isatty(fd)) return false;
FMT_FUNC bool write_console(int fd, string_view text) {
auto u16 = utf8_to_utf16(text);
auto written = dword();
return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
static_cast<uint32_t>(u16.size()), &written, nullptr);
static_cast<dword>(u16.size()), nullptr, nullptr) != 0;
}
FMT_FUNC auto write_console(std::FILE* f, string_view text) -> bool {
return write_console(_fileno(f), text);
}
#endif
#ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding.
FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt,
basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f);
detail::vformat_to(buffer, fmt, args);
fwrite_fully(buffer.data(), buffer.size(), f);
}
#endif
FMT_FUNC void print(std::FILE* f, string_view text) {
if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
#ifdef _WIN32
int fd = _fileno(f);
if (_isatty(fd)) {
std::fflush(f);
if (write_console(fd, text)) return;
}
#endif
fwrite_fully(text.data(), text.size(), f);
}
} // namespace detail

File diff suppressed because it is too large Load Diff

View File

@ -10,19 +10,50 @@
#include <fstream> // std::filebuf
#if defined(_WIN32) && defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
# include <__std_stream>
#ifdef _WIN32
# ifdef __GLIBCXX__
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# include <io.h>
#endif
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Streambuf> class formatbuf : public Streambuf {
private:
using char_type = typename Streambuf::char_type;
using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
using int_type = typename Streambuf::int_type;
using traits_type = typename Streambuf::traits_type;
buffer<char_type>& buffer_;
public:
explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
protected:
// The put area is always empty. This makes the implementation simpler and has
// the advantage that the streambuf and the buffer are always in sync and
// sputc never writes into uninitialized memory. A disadvantage is that each
// call to sputc always results in a (virtual) call to overflow. There is no
// disadvantage here for sputn since this always results in a call to xsputn.
auto overflow(int_type ch) -> int_type override {
if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<char_type>(ch));
return ch;
}
auto xsputn(const char_type* s, streamsize count) -> streamsize override {
buffer_.append(s, s + count);
return count;
}
};
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
@ -37,36 +68,40 @@ class file_access {
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
template class file_access<file_access_tag, std::__stdoutbuf<char>,
&std::__stdoutbuf<char>::__file_>;
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
#endif
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
-> bool {
FILE* f = nullptr;
#if FMT_MSC_VERSION
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
FILE* c_file;
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
if (c_file) return write_console(c_file, data);
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf()))
if (FILE* f = get_file(*buf)) return write_console(f, data);
#else
ignore_unused(os, data);
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline bool write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) {
inline auto write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) -> bool {
return false;
}
@ -87,18 +122,19 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value,
locale_ref loc = locale_ref()) {
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>());
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view { const T& value; };
template <typename T> struct streamed_view {
const T& value;
};
} // namespace detail
@ -111,7 +147,7 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value, ctx.locale());
detail::format_value(buffer, value);
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
@ -140,7 +176,7 @@ struct formatter<detail::streamed_view<T>, Char>
\endrst
*/
template <typename T>
auto streamed(const T& value) -> detail::streamed_view<T> {
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
@ -155,7 +191,7 @@ inline void vprint_directly(std::ostream& os, string_view format_str,
} // namespace detail
FMT_MODULE_EXPORT template <typename Char>
FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
@ -174,7 +210,7 @@ void vprint(std::basic_ostream<Char>& os,
fmt::print(cerr, "Don't {}!", "panic");
\endrst
*/
FMT_MODULE_EXPORT template <typename... T>
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmt::make_format_args(args...);
if (detail::is_utf8())
@ -183,7 +219,7 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
detail::vprint_directly(os, fmt, vargs);
}
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
@ -191,12 +227,12 @@ void print(std::wostream& os,
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
}
FMT_MODULE_EXPORT template <typename... T>
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,

View File

@ -16,22 +16,22 @@
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter { printf_formatter() = delete; };
template <typename Char>
class basic_printf_parse_context : public basic_format_parse_context<Char> {
using basic_format_parse_context<Char>::basic_format_parse_context;
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename OutputIt, typename Char> class basic_printf_context {
template <typename Char> class basic_printf_context {
private:
OutputIt out_;
detail::buffer_appender<Char> out_;
basic_format_args<basic_printf_context> args_;
static_assert(std::is_same<Char, char>::value ||
std::is_same<Char, wchar_t>::value,
"Unsupported code unit type.");
public:
using char_type = Char;
using format_arg = basic_format_arg<basic_printf_context>;
using parse_context_type = basic_printf_parse_context<Char>;
using parse_context_type = basic_format_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
/**
@ -40,75 +40,77 @@ template <typename OutputIt, typename Char> class basic_printf_context {
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out,
basic_printf_context(detail::buffer_appender<Char> out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
auto out() -> detail::buffer_appender<Char> { return out_; }
void advance_to(detail::buffer_appender<Char>) {}
detail::locale_ref locale() { return {}; }
auto locale() -> detail::locale_ref { return {}; }
format_arg arg(int id) const { return args_.get(id); }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
}
FMT_CONSTEXPR void on_error(const char* message) {
detail::error_handler().on_error(message);
}
};
FMT_BEGIN_DETAIL_NAMESPACE
namespace detail {
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
template <typename T> static bool fits_in_int(T value) {
template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = max_value<int>();
return value <= max;
}
static bool fits_in_int(bool) { return true; }
static auto fits_in_int(bool) -> bool { return true; }
};
template <> struct int_checker<true> {
template <typename T> static bool fits_in_int(T value) {
template <typename T> static auto fits_in_int(T value) -> bool {
return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>();
}
static bool fits_in_int(int) { return true; }
static auto fits_in_int(int) -> bool { return true; }
};
class printf_precision_handler {
public:
struct printf_precision_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
int operator()(T value) {
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
throw_format_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
int operator()(T) {
auto operator()(T) -> int {
throw_format_error("precision is not integer");
return 0;
}
};
// An argument visitor that returns true iff arg is a zero integer.
class is_zero_int {
public:
struct is_zero_int {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
bool operator()(T value) {
auto operator()(T value) -> bool {
return value == 0;
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
bool operator()(T) {
auto operator()(T) -> bool {
return false;
}
};
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
template <> struct make_unsigned_or_bool<bool> {
using type = bool;
};
template <typename T, typename Context> class arg_converter {
private:
@ -132,22 +134,23 @@ template <typename T, typename Context> class arg_converter {
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
arg_ = detail::make_arg<Context>(
static_cast<int>(static_cast<target_type>(value)));
auto n = static_cast<int>(static_cast<target_type>(value));
arg_ = detail::make_arg<Context>(n);
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = detail::make_arg<Context>(
static_cast<unsigned>(static_cast<unsigned_type>(value)));
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else {
arg_ = detail::make_arg<Context>(
static_cast<typename make_unsigned_or_bool<U>::type>(value));
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
}
}
@ -175,8 +178,8 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
arg_ = detail::make_arg<Context>(
static_cast<typename Context::char_type>(value));
auto c = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -186,8 +189,8 @@ template <typename Context> class char_converter {
// An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise.
template <typename Char> struct get_cstring {
template <typename T> const Char* operator()(T) { return nullptr; }
const Char* operator()(const Char* s) { return s; }
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
auto operator()(const Char* s) -> const Char* { return s; }
};
// Checks if an argument is a valid printf width specifier and sets
@ -200,7 +203,7 @@ template <typename Char> class printf_width_handler {
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
specs_.align = align::left;
@ -212,7 +215,7 @@ template <typename Char> class printf_width_handler {
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
unsigned operator()(T) {
auto operator()(T) -> unsigned {
throw_format_error("width is not integer");
return 0;
}
@ -227,80 +230,85 @@ auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
}
// The ``printf`` argument formatter.
template <typename OutputIt, typename Char>
template <typename Char>
class printf_arg_formatter : public arg_formatter<Char> {
private:
using base = arg_formatter<Char>;
using context_type = basic_printf_context<OutputIt, Char>;
using context_type = basic_printf_context<Char>;
context_type& context_;
OutputIt write_null_pointer(bool is_string = false) {
void write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.type = presentation_type::none;
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
}
public:
printf_arg_formatter(OutputIt iter, format_specs<Char>& s, context_type& ctx)
printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); }
void operator()(monostate value) { base::operator()(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
OutputIt operator()(T value) {
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (std::is_same<T, Char>::value) {
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
if (!std::is_same<T, Char>::value) {
base::operator()(value);
return;
}
return base::operator()(value);
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
write<Char>(this->out, static_cast<Char>(value), fmt_specs);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
OutputIt operator()(T value) {
return base::operator()(value);
void operator()(T value) {
base::operator()(value);
}
/** Formats a null-terminated C string. */
OutputIt operator()(const char* value) {
if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != presentation_type::pointer);
void operator()(const char* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
/** Formats a null-terminated wide C string. */
OutputIt operator()(const wchar_t* value) {
if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != presentation_type::pointer);
void operator()(const wchar_t* value) {
if (value)
base::operator()(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
}
OutputIt operator()(basic_string_view<Char> value) {
return base::operator()(value);
}
void operator()(basic_string_view<Char> value) { base::operator()(value); }
/** Formats a pointer. */
OutputIt operator()(const void* value) {
return value ? base::operator()(value) : write_null_pointer();
void operator()(const void* value) {
if (value)
base::operator()(value);
else
write_null_pointer();
}
/** Formats an argument of a custom (user-defined) type. */
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx =
basic_printf_parse_context<Char>(basic_string_view<Char>());
void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({});
handle.format(parse_ctx, context_);
return this->out;
}
};
@ -318,9 +326,7 @@ void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
specs.fill[0] = '0';
break;
case ' ':
if (specs.sign != sign::plus) {
specs.sign = sign::space;
}
if (specs.sign != sign::plus) specs.sign = sign::space;
break;
case '#':
specs.alt = true;
@ -332,8 +338,8 @@ void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
}
template <typename Char, typename GetArg>
int parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
GetArg get_arg) {
auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
GetArg get_arg) -> int {
int arg_index = -1;
Char c = *it;
if (c >= '0' && c <= '9') {
@ -414,8 +420,8 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using iterator = buffer_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<iterator, Char>(out, args);
auto parse_ctx = basic_printf_parse_context<Char>(format);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
@ -437,12 +443,11 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
}
Char c = *it++;
if (it != end && *it == c) {
out = write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
start = ++it;
continue;
}
out =
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs<Char>();
specs.align = align::right;
@ -469,16 +474,17 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral())
specs.fill[0] =
' '; // Ignore '0' flag for non-numeric types or if '-' present.
if (specs.precision >= 0 && arg.is_integral()) {
// Ignore '0' for non-numeric types or if '-' present.
specs.fill[0] = ' ';
}
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
arg = make_arg<basic_printf_context<iterator, Char>>(
basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision)));
auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv);
}
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
if (specs.fill[0] == '0') {
@ -540,8 +546,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
type = 'd';
break;
case 'c':
visit_format_arg(
char_converter<basic_printf_context<iterator, Char>>(arg), arg);
visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
break;
}
}
@ -552,19 +557,14 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
start = it;
// Format argument.
out = visit_format_arg(
printf_arg_formatter<iterator, Char>(out, specs, context), arg);
visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
}
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
FMT_END_DETAIL_NAMESPACE
} // namespace detail
template <typename Char>
using basic_printf_context_t =
basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
using printf_context = basic_printf_context<char>;
using wprintf_context = basic_printf_context<wchar_t>;
using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>;
@ -581,25 +581,20 @@ inline auto make_printf_args(const T&... args)
return {args...};
}
/**
\rst
Constructs an `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::wprintf_args`.
\endrst
*/
// DEPRECATED!
template <typename... T>
inline auto make_wprintf_args(const T&... args)
-> format_arg_store<wprintf_context, T...> {
return {args...};
}
template <typename S, typename Char = char_t<S>>
template <typename Char>
inline auto vsprintf(
const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, detail::to_string_view(fmt), args);
detail::vprintf(buf, fmt, args);
return to_string(buf);
}
@ -615,18 +610,17 @@ inline auto vsprintf(
template <typename S, typename... T,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
using context = basic_printf_context_t<Char>;
return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<context>(args...));
fmt::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename S, typename Char = char_t<S>>
template <typename Char>
inline auto vfprintf(
std::FILE* f, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
std::FILE* f, basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, detail::to_string_view(fmt), args);
detail::vprintf(buf, fmt, args);
size_t size = buf.size();
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1
@ -644,17 +638,16 @@ inline auto vfprintf(
*/
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
using context = basic_printf_context_t<Char>;
return vfprintf(f, detail::to_string_view(fmt),
fmt::make_format_args<context>(args...));
fmt::make_format_args<basic_printf_context<Char>>(args...));
}
template <typename S, typename Char = char_t<S>>
inline auto vprintf(
const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
template <typename Char>
FMT_DEPRECATED inline auto vprintf(
basic_string_view<Char> fmt,
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
-> int {
return vfprintf(stdout, detail::to_string_view(fmt), args);
return vfprintf(stdout, fmt, args);
}
/**
@ -666,11 +659,14 @@ inline auto vprintf(
fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst
*/
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
inline auto printf(const S& fmt, const T&... args) -> int {
return vprintf(
detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_wprintf_args(args...));
}
FMT_END_EXPORT

View File

@ -1,13 +1,9 @@
// Formatting library for C++ - experimental range support
// Formatting library for C++ - range and tuple support
//
// Copyright (c) 2012 - present, Victor Zverovich
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
// All rights reserved.
//
// For the license information refer to format.h.
//
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
// All Rights Reserved
// {fmt} support for ranges, containers and types tuple interface.
#ifndef FMT_RANGES_H_
#define FMT_RANGES_H_
@ -187,7 +183,7 @@ template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
template <typename T, T... N> struct integer_sequence {
using value_type = T;
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
};
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
@ -211,15 +207,15 @@ class is_tuple_formattable_ {
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... Is>
static std::true_type check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>);
static std::false_type check2(...);
static auto check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>) -> std::true_type;
static auto check2(...) -> std::false_type;
template <std::size_t... Is>
static decltype(check2(
static auto check(index_sequence<Is...>) -> decltype(check2(
index_sequence<Is...>{},
integer_sequence<
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{})) check(index_sequence<Is...>);
integer_sequence<bool,
(is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{}));
public:
static constexpr const bool value =
@ -421,6 +417,12 @@ struct is_formattable_delayed
#endif
} // namespace detail
template <typename...> struct conjunction : std::true_type {};
template <typename P> struct conjunction<P> : P {};
template <typename P1, typename... Pn>
struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
template <typename T, typename Char, typename Enable = void>
struct range_formatter;
@ -486,7 +488,8 @@ struct range_formatter<
for (; it != end; ++it) {
if (i > 0) out = detail::copy_str<Char>(separator_, out);
ctx.advance_to(out);
out = underlying_.format(mapper.map(*it), ctx);
auto&& item = *it;
out = underlying_.format(mapper.map(item), ctx);
++i;
}
out = detail::copy_str<Char>(closing_bracket_, out);
@ -668,8 +671,11 @@ template <typename Container> struct all {
} // namespace detail
template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<detail::is_container_adaptor_like<T>::value>>
struct formatter<
T, Char,
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
bool_constant<range_format_kind<T, Char>::value ==
range_format::disabled>>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>

View File

@ -8,6 +8,8 @@
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include <atomic>
#include <bitset>
#include <cstdlib>
#include <exception>
#include <memory>
@ -15,7 +17,9 @@
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>
#include "format.h"
#include "ostream.h"
#if FMT_HAS_INCLUDE(<version>)
@ -34,6 +38,10 @@
# endif
#endif
#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
# include <source_location>
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
@ -44,67 +52,155 @@
# endif
#endif
#ifdef __cpp_lib_filesystem
// Check if typeid is available.
#ifndef FMT_USE_TYPEID
// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI.
# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || FMT_MSC_VERSION || \
defined(__INTEL_RTTI__) || defined(__RTTI)
# define FMT_USE_TYPEID 1
# else
# define FMT_USE_TYPEID 0
# endif
#endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
#ifndef FMT_CPP_LIB_FILESYSTEM
# ifdef __cpp_lib_filesystem
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
# else
# define FMT_CPP_LIB_FILESYSTEM 0
# endif
#endif
#ifndef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
# else
# define FMT_CPP_LIB_VARIANT 0
# endif
#endif
#if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char>
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
return p.string<Char>();
}
template <typename Char, typename PathChar>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p) {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
# ifdef _WIN32
template <>
inline void write_escaped_path<char>(memory_buffer& quoted,
const std::filesystem::path& p) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
// Convert UTF-16 to UTF-8.
if (!unicode_to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}))
FMT_THROW(std::runtime_error("invalid utf16"));
}
# endif
template <>
inline void write_escaped_path<std::filesystem::path::value_type>(
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
const std::filesystem::path& p) {
write_escaped_string<std::filesystem::path::value_type>(
std::back_inserter(quoted), p.native());
const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
FMT_ASSERT(valid, "invalid utf16");
} else if constexpr (std::is_same_v<Char, PathChar>) {
write_escaped_string<std::filesystem::path::value_type>(
std::back_inserter(quoted), native);
} else {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
}
} // namespace detail
FMT_MODULE_EXPORT
template <typename Char>
struct formatter<std::filesystem::path, Char>
: formatter<basic_string_view<Char>> {
FMT_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs<Char> specs_;
detail::arg_ref<Char> width_ref_;
bool debug_ = false;
char path_type_ = 0;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto out = formatter<basic_string_view<Char>>::parse(ctx);
this->set_debug_format(false);
return out;
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && (*it == 'g')) path_type_ = *it++;
return it;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
typename FormatContext::iterator {
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
auto specs = specs_;
# ifdef _WIN32
auto path_string = !path_type_ ? p.native() : p.generic_wstring();
# else
auto path_string = !path_type_ ? p.native() : p.generic_string();
# endif
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
}
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p);
return formatter<basic_string_view<Char>>::format(
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
detail::write_escaped_path(quoted, p, path_string);
return detail::write(ctx.out(),
basic_string_view<Char>(quoted.data(), quoted.size()),
specs);
}
};
FMT_END_NAMESPACE
#endif
#endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
FMT_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
private:
// Functor because C++11 doesn't support generic lambdas.
struct writer {
const std::bitset<N>& bs;
template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) {
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out;
}
};
public:
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return write_padded(ctx, writer{bs});
}
};
FMT_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
@ -132,7 +228,7 @@ struct formatter<std::optional<T>, Char,
}
template <typename FormatContext>
auto format(std::optional<T> const& opt, FormatContext& ctx) const
auto format(const std::optional<T>& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
@ -146,24 +242,33 @@ struct formatter<std::optional<T>, Char,
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#ifdef __cpp_lib_variant
#ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_EXPORT
template <> struct formatter<std::source_location> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
auto format(const std::source_location& loc, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write<Char>(out, "monostate");
out = detail::write(out, loc.file_name());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.line());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.column());
out = detail::write(out, ": ");
out = detail::write(out, loc.function_name());
return out;
}
};
FMT_END_NAMESPACE
#endif
#if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
@ -197,6 +302,7 @@ auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt {
}
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
@ -206,7 +312,21 @@ template <typename T, typename C> struct is_variant_formattable {
detail::is_variant_formattable_<T, C>::value;
};
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
return detail::write<Char>(ctx.out(), "monostate");
}
};
FMT_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
@ -223,13 +343,14 @@ struct formatter<
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
try {
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
} catch (const std::bad_variant_access&) {
}
FMT_CATCH(const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
@ -237,10 +358,10 @@ struct formatter<
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_variant
#endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
@ -258,10 +379,10 @@ template <typename Char> struct formatter<std::error_code, Char> {
}
};
FMT_MODULE_EXPORT
FMT_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char,
T, Char, // DEPRECATED! Mixing code unit types.
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
@ -274,7 +395,7 @@ struct formatter<
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = true;
with_typename_ = FMT_USE_TYPEID != 0;
}
return it;
}
@ -287,11 +408,12 @@ struct formatter<
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);
#if FMT_USE_TYPEID
const std::type_info& ti = typeid(ex);
#ifdef FMT_HAS_ABI_CXA_DEMANGLE
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
@ -327,23 +449,89 @@ struct formatter<
demangled_name_view = string_view(ti.name());
}
out = detail::write_bytes(out, demangled_name_view, spec);
#elif FMT_MSC_VERSION
# elif FMT_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
#else
# else
out = detail::write_bytes(out, string_view(ti.name()), spec);
# endif
*out++ = ':';
*out++ = ' ';
return detail::write_bytes(out, string_view(ex.what()), spec);
#endif
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, Char(' '));
out = detail::write_bytes(out, string_view(ex.what()), spec);
return out;
}
};
FMT_END_NAMESPACE
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization.
FMT_EXPORT
template <typename BitRef, typename Char>
struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
: formatter<bool, Char> {
template <typename FormatContext>
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v, ctx);
}
};
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
: formatter<T, Char> {
template <typename FormatContext>
auto format(const std::atomic<T>& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<T, Char>::format(v.load(), ctx);
}
};
#ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext>
auto format(const std::atomic_flag& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v.test(), ctx);
}
};
#endif // __cpp_lib_atomic_flag_test
FMT_END_NAMESPACE
#endif // FMT_STD_H_

504
source/extern/malloc.c vendored Normal file
View File

@ -0,0 +1,504 @@
/* malloc.c - Memory allocator - Public Domain - 2016 Mattias Jansson
*
* This library provides a cross-platform lock free thread caching malloc implementation in C11.
* The latest source code is always available at
*
* https://github.com/mjansson/rpmalloc
*
* This library is put in the public domain; you can redistribute it and/or modify it without any restrictions.
*
*/
//
// This file provides overrides for the standard library malloc entry points for C and new/delete operators for C++
// It also provides automatic initialization/finalization of process and threads
//
#if defined(__TINYC__)
#include <sys/types.h>
#endif
#ifndef ARCH_64BIT
# if defined(__LLP64__) || defined(__LP64__) || defined(_WIN64)
# define ARCH_64BIT 1
# else
# define ARCH_64BIT 0
_Static_assert(sizeof(size_t) == 4, "Data type size mismatch");
_Static_assert(sizeof(void*) == 4, "Data type size mismatch");
# endif
#endif
#if (defined(__GNUC__) || defined(__clang__))
#pragma GCC visibility push(default)
#endif
#define USE_IMPLEMENT 1
#define USE_INTERPOSE 0
#define USE_ALIAS 0
#if defined(__APPLE__)
#undef USE_INTERPOSE
#define USE_INTERPOSE 1
typedef struct interpose_t {
void* new_func;
void* orig_func;
} interpose_t;
#define MAC_INTERPOSE_PAIR(newf, oldf) { (void*)newf, (void*)oldf }
#define MAC_INTERPOSE_SINGLE(newf, oldf) \
__attribute__((used)) static const interpose_t macinterpose##newf##oldf \
__attribute__ ((section("__DATA, __interpose"))) = MAC_INTERPOSE_PAIR(newf, oldf)
#endif
#if !defined(_WIN32) && !defined(__APPLE__)
#undef USE_IMPLEMENT
#undef USE_ALIAS
#define USE_IMPLEMENT 0
#define USE_ALIAS 1
#endif
#ifdef _MSC_VER
#pragma warning (disable : 4100)
#undef malloc
#undef free
#undef calloc
#define RPMALLOC_RESTRICT __declspec(restrict)
#else
#define RPMALLOC_RESTRICT
#endif
#if ENABLE_OVERRIDE
typedef struct rp_nothrow_t { int __dummy; } rp_nothrow_t;
#if USE_IMPLEMENT
extern inline RPMALLOC_RESTRICT void* RPMALLOC_CDECL malloc(size_t size) { return rpmalloc(size); }
extern inline RPMALLOC_RESTRICT void* RPMALLOC_CDECL calloc(size_t count, size_t size) { return rpcalloc(count, size); }
extern inline RPMALLOC_RESTRICT void* RPMALLOC_CDECL realloc(void* ptr, size_t size) { return rprealloc(ptr, size); }
extern inline void* RPMALLOC_CDECL reallocf(void* ptr, size_t size) { return rprealloc(ptr, size); }
extern inline void* RPMALLOC_CDECL aligned_alloc(size_t alignment, size_t size) { return rpaligned_alloc(alignment, size); }
extern inline void* RPMALLOC_CDECL memalign(size_t alignment, size_t size) { return rpmemalign(alignment, size); }
extern inline int RPMALLOC_CDECL posix_memalign(void** memptr, size_t alignment, size_t size) { return rpposix_memalign(memptr, alignment, size); }
extern inline void RPMALLOC_CDECL free(void* ptr) { rpfree(ptr); }
extern inline void RPMALLOC_CDECL cfree(void* ptr) { rpfree(ptr); }
extern inline size_t RPMALLOC_CDECL malloc_usable_size(void* ptr) { return rpmalloc_usable_size(ptr); }
extern inline size_t RPMALLOC_CDECL malloc_size(void* ptr) { return rpmalloc_usable_size(ptr); }
#ifdef _WIN32
extern inline RPMALLOC_RESTRICT void* RPMALLOC_CDECL _malloc_base(size_t size) { return rpmalloc(size); }
extern inline void RPMALLOC_CDECL _free_base(void* ptr) { rpfree(ptr); }
extern inline RPMALLOC_RESTRICT void* RPMALLOC_CDECL _calloc_base(size_t count, size_t size) { return rpcalloc(count, size); }
extern inline size_t RPMALLOC_CDECL _msize(void* ptr) { return rpmalloc_usable_size(ptr); }
extern inline size_t RPMALLOC_CDECL _msize_base(void* ptr) { return rpmalloc_usable_size(ptr); }
extern inline RPMALLOC_RESTRICT void* RPMALLOC_CDECL _realloc_base(void* ptr, size_t size) { return rprealloc(ptr, size); }
#endif
#ifdef _WIN32
// For Windows, #include <rpnew.h> in one source file to get the C++ operator overrides implemented in your module
#else
// Overload the C++ operators using the mangled names (https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling)
// operators delete and delete[]
#define RPDEFVIS __attribute__((visibility("default")))
extern void _ZdlPv(void* p); void RPDEFVIS _ZdlPv(void* p) { rpfree(p); }
extern void _ZdaPv(void* p); void RPDEFVIS _ZdaPv(void* p) { rpfree(p); }
#if ARCH_64BIT
// 64-bit operators new and new[], normal and aligned
extern void* _Znwm(uint64_t size); void* RPDEFVIS _Znwm(uint64_t size) { return rpmalloc(size); }
extern void* _Znam(uint64_t size); void* RPDEFVIS _Znam(uint64_t size) { return rpmalloc(size); }
extern void* _Znwmm(uint64_t size, uint64_t align); void* RPDEFVIS _Znwmm(uint64_t size, uint64_t align) { return rpaligned_alloc(align, size); }
extern void* _Znamm(uint64_t size, uint64_t align); void* RPDEFVIS _Znamm(uint64_t size, uint64_t align) { return rpaligned_alloc(align, size); }
extern void* _ZnwmSt11align_val_t(uint64_t size, uint64_t align); void* RPDEFVIS _ZnwmSt11align_val_t(uint64_t size, uint64_t align) { return rpaligned_alloc(align, size); }
extern void* _ZnamSt11align_val_t(uint64_t size, uint64_t align); void* RPDEFVIS _ZnamSt11align_val_t(uint64_t size, uint64_t align) { return rpaligned_alloc(align, size); }
extern void* _ZnwmRKSt9nothrow_t(uint64_t size, rp_nothrow_t t); void* RPDEFVIS _ZnwmRKSt9nothrow_t(uint64_t size, rp_nothrow_t t) { (void)sizeof(t); return rpmalloc(size); }
extern void* _ZnamRKSt9nothrow_t(uint64_t size, rp_nothrow_t t); void* RPDEFVIS _ZnamRKSt9nothrow_t(uint64_t size, rp_nothrow_t t) { (void)sizeof(t); return rpmalloc(size); }
extern void* _ZnwmSt11align_val_tRKSt9nothrow_t(uint64_t size, uint64_t align, rp_nothrow_t t); void* RPDEFVIS _ZnwmSt11align_val_tRKSt9nothrow_t(uint64_t size, uint64_t align, rp_nothrow_t t) { (void)sizeof(t); return rpaligned_alloc(align, size); }
extern void* _ZnamSt11align_val_tRKSt9nothrow_t(uint64_t size, uint64_t align, rp_nothrow_t t); void* RPDEFVIS _ZnamSt11align_val_tRKSt9nothrow_t(uint64_t size, uint64_t align, rp_nothrow_t t) { (void)sizeof(t); return rpaligned_alloc(align, size); }
// 64-bit operators sized delete and delete[], normal and aligned
extern void _ZdlPvm(void* p, uint64_t size); void RPDEFVIS _ZdlPvm(void* p, uint64_t size) { rpfree(p); (void)sizeof(size); }
extern void _ZdaPvm(void* p, uint64_t size); void RPDEFVIS _ZdaPvm(void* p, uint64_t size) { rpfree(p); (void)sizeof(size); }
extern void _ZdlPvSt11align_val_t(void* p, uint64_t align); void RPDEFVIS _ZdlPvSt11align_val_t(void* p, uint64_t align) { rpfree(p); (void)sizeof(align); }
extern void _ZdaPvSt11align_val_t(void* p, uint64_t align); void RPDEFVIS _ZdaPvSt11align_val_t(void* p, uint64_t align) { rpfree(p); (void)sizeof(align); }
extern void _ZdlPvmSt11align_val_t(void* p, uint64_t size, uint64_t align); void RPDEFVIS _ZdlPvmSt11align_val_t(void* p, uint64_t size, uint64_t align) { rpfree(p); (void)sizeof(size); (void)sizeof(align); }
extern void _ZdaPvmSt11align_val_t(void* p, uint64_t size, uint64_t align); void RPDEFVIS _ZdaPvmSt11align_val_t(void* p, uint64_t size, uint64_t align) { rpfree(p); (void)sizeof(size); (void)sizeof(align); }
#else
// 32-bit operators new and new[], normal and aligned
extern void* _Znwj(uint32_t size); void* RPDEFVIS _Znwj(uint32_t size) { return rpmalloc(size); }
extern void* _Znaj(uint32_t size); void* RPDEFVIS _Znaj(uint32_t size) { return rpmalloc(size); }
extern void* _Znwjj(uint32_t size, uint32_t align); void* RPDEFVIS _Znwjj(uint32_t size, uint32_t align) { return rpaligned_alloc(align, size); }
extern void* _Znajj(uint32_t size, uint32_t align); void* RPDEFVIS _Znajj(uint32_t size, uint32_t align) { return rpaligned_alloc(align, size); }
extern void* _ZnwjSt11align_val_t(size_t size, size_t align); void* RPDEFVIS _ZnwjSt11align_val_t(size_t size, size_t align) { return rpaligned_alloc(align, size); }
extern void* _ZnajSt11align_val_t(size_t size, size_t align); void* RPDEFVIS _ZnajSt11align_val_t(size_t size, size_t align) { return rpaligned_alloc(align, size); }
extern void* _ZnwjRKSt9nothrow_t(size_t size, rp_nothrow_t t); void* RPDEFVIS _ZnwjRKSt9nothrow_t(size_t size, rp_nothrow_t t) { (void)sizeof(t); return rpmalloc(size); }
extern void* _ZnajRKSt9nothrow_t(size_t size, rp_nothrow_t t); void* RPDEFVIS _ZnajRKSt9nothrow_t(size_t size, rp_nothrow_t t) { (void)sizeof(t); return rpmalloc(size); }
extern void* _ZnwjSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t); void* RPDEFVIS _ZnwjSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t) { (void)sizeof(t); return rpaligned_alloc(align, size); }
extern void* _ZnajSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t); void* RPDEFVIS _ZnajSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t) { (void)sizeof(t); return rpaligned_alloc(align, size); }
// 32-bit operators sized delete and delete[], normal and aligned
extern void _ZdlPvj(void* p, uint64_t size); void RPDEFVIS _ZdlPvj(void* p, uint64_t size) { rpfree(p); (void)sizeof(size); }
extern void _ZdaPvj(void* p, uint64_t size); void RPDEFVIS _ZdaPvj(void* p, uint64_t size) { rpfree(p); (void)sizeof(size); }
extern void _ZdlPvSt11align_val_t(void* p, uint32_t align); void RPDEFVIS _ZdlPvSt11align_val_t(void* p, uint64_t a) { rpfree(p); (void)sizeof(align); }
extern void _ZdaPvSt11align_val_t(void* p, uint32_t align); void RPDEFVIS _ZdaPvSt11align_val_t(void* p, uint64_t a) { rpfree(p); (void)sizeof(align); }
extern void _ZdlPvjSt11align_val_t(void* p, uint32_t size, uint32_t align); void RPDEFVIS _ZdlPvjSt11align_val_t(void* p, uint64_t size, uint64_t align) { rpfree(p); (void)sizeof(size); (void)sizeof(a); }
extern void _ZdaPvjSt11align_val_t(void* p, uint32_t size, uint32_t align); void RPDEFVIS _ZdaPvjSt11align_val_t(void* p, uint64_t size, uint64_t align) { rpfree(p); (void)sizeof(size); (void)sizeof(a); }
#endif
#endif
#endif
#if USE_INTERPOSE || USE_ALIAS
static void* rpmalloc_nothrow(size_t size, rp_nothrow_t t) { (void)sizeof(t); return rpmalloc(size); }
static void* rpaligned_alloc_reverse(size_t size, size_t align) { return rpaligned_alloc(align, size); }
static void* rpaligned_alloc_reverse_nothrow(size_t size, size_t align, rp_nothrow_t t) { (void)sizeof(t); return rpaligned_alloc(align, size); }
static void rpfree_size(void* p, size_t size) { (void)sizeof(size); rpfree(p); }
static void rpfree_aligned(void* p, size_t align) { (void)sizeof(align); rpfree(p); }
static void rpfree_size_aligned(void* p, size_t size, size_t align) { (void)sizeof(size); (void)sizeof(align); rpfree(p); }
#endif
#if USE_INTERPOSE
__attribute__((used)) static const interpose_t macinterpose_malloc[]
__attribute__ ((section("__DATA, __interpose"))) = {
//new and new[]
MAC_INTERPOSE_PAIR(rpmalloc, _Znwm),
MAC_INTERPOSE_PAIR(rpmalloc, _Znam),
MAC_INTERPOSE_PAIR(rpaligned_alloc_reverse, _Znwmm),
MAC_INTERPOSE_PAIR(rpaligned_alloc_reverse, _Znamm),
MAC_INTERPOSE_PAIR(rpmalloc_nothrow, _ZnwmRKSt9nothrow_t),
MAC_INTERPOSE_PAIR(rpmalloc_nothrow, _ZnamRKSt9nothrow_t),
MAC_INTERPOSE_PAIR(rpaligned_alloc_reverse, _ZnwmSt11align_val_t),
MAC_INTERPOSE_PAIR(rpaligned_alloc_reverse, _ZnamSt11align_val_t),
MAC_INTERPOSE_PAIR(rpaligned_alloc_reverse_nothrow, _ZnwmSt11align_val_tRKSt9nothrow_t),
MAC_INTERPOSE_PAIR(rpaligned_alloc_reverse_nothrow, _ZnamSt11align_val_tRKSt9nothrow_t),
//delete and delete[]
MAC_INTERPOSE_PAIR(rpfree, _ZdlPv),
MAC_INTERPOSE_PAIR(rpfree, _ZdaPv),
MAC_INTERPOSE_PAIR(rpfree_size, _ZdlPvm),
MAC_INTERPOSE_PAIR(rpfree_size, _ZdaPvm),
MAC_INTERPOSE_PAIR(rpfree_aligned, _ZdlPvSt11align_val_t),
MAC_INTERPOSE_PAIR(rpfree_aligned, _ZdaPvSt11align_val_t),
MAC_INTERPOSE_PAIR(rpfree_size_aligned, _ZdlPvmSt11align_val_t),
MAC_INTERPOSE_PAIR(rpfree_size_aligned, _ZdaPvmSt11align_val_t),
//libc entry points
MAC_INTERPOSE_PAIR(rpmalloc, malloc),
MAC_INTERPOSE_PAIR(rpmalloc, calloc),
MAC_INTERPOSE_PAIR(rprealloc, realloc),
MAC_INTERPOSE_PAIR(rprealloc, reallocf),
#if defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15
MAC_INTERPOSE_PAIR(rpaligned_alloc, aligned_alloc),
#endif
MAC_INTERPOSE_PAIR(rpmemalign, memalign),
MAC_INTERPOSE_PAIR(rpposix_memalign, posix_memalign),
MAC_INTERPOSE_PAIR(rpfree, free),
MAC_INTERPOSE_PAIR(rpfree, cfree),
MAC_INTERPOSE_PAIR(rpmalloc_usable_size, malloc_usable_size),
MAC_INTERPOSE_PAIR(rpmalloc_usable_size, malloc_size)
};
#endif
#if USE_ALIAS
#define RPALIAS(fn) __attribute__((alias(#fn), used, visibility("default")));
// Alias the C++ operators using the mangled names (https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling)
// operators delete and delete[]
void _ZdlPv(void* p) RPALIAS(rpfree)
void _ZdaPv(void* p) RPALIAS(rpfree)
#if ARCH_64BIT
// 64-bit operators new and new[], normal and aligned
void* _Znwm(uint64_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1) RPALIAS(rpmalloc)
void* _Znam(uint64_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1) RPALIAS(rpmalloc)
void* _Znwmm(uint64_t size, uint64_t align) RPALIAS(rpaligned_alloc_reverse)
void* _Znamm(uint64_t size, uint64_t align) RPALIAS(rpaligned_alloc_reverse)
void* _ZnwmSt11align_val_t(size_t size, size_t align) RPALIAS(rpaligned_alloc_reverse)
void* _ZnamSt11align_val_t(size_t size, size_t align) RPALIAS(rpaligned_alloc_reverse)
void* _ZnwmRKSt9nothrow_t(size_t size, rp_nothrow_t t) RPALIAS(rpmalloc_nothrow)
void* _ZnamRKSt9nothrow_t(size_t size, rp_nothrow_t t) RPALIAS(rpmalloc_nothrow)
void* _ZnwmSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t) RPALIAS(rpaligned_alloc_reverse_nothrow)
void* _ZnamSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t) RPALIAS(rpaligned_alloc_reverse_nothrow)
// 64-bit operators delete and delete[], sized and aligned
void _ZdlPvm(void* p, size_t n) RPALIAS(rpfree_size)
void _ZdaPvm(void* p, size_t n) RPALIAS(rpfree_size)
void _ZdlPvSt11align_val_t(void* p, size_t a) RPALIAS(rpfree_aligned)
void _ZdaPvSt11align_val_t(void* p, size_t a) RPALIAS(rpfree_aligned)
void _ZdlPvmSt11align_val_t(void* p, size_t n, size_t a) RPALIAS(rpfree_size_aligned)
void _ZdaPvmSt11align_val_t(void* p, size_t n, size_t a) RPALIAS(rpfree_size_aligned)
#else
// 32-bit operators new and new[], normal and aligned
void* _Znwj(uint32_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1) RPALIAS(rpmalloc)
void* _Znaj(uint32_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1) RPALIAS(rpmalloc)
void* _Znwjj(uint32_t size, uint32_t align) RPALIAS(rpaligned_alloc_reverse)
void* _Znajj(uint32_t size, uint32_t align) RPALIAS(rpaligned_alloc_reverse)
void* _ZnwjSt11align_val_t(size_t size, size_t align) RPALIAS(rpaligned_alloc_reverse)
void* _ZnajSt11align_val_t(size_t size, size_t align) RPALIAS(rpaligned_alloc_reverse)
void* _ZnwjRKSt9nothrow_t(size_t size, rp_nothrow_t t) RPALIAS(rpmalloc_nothrow)
void* _ZnajRKSt9nothrow_t(size_t size, rp_nothrow_t t) RPALIAS(rpmalloc_nothrow)
void* _ZnwjSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t) RPALIAS(rpaligned_alloc_reverse_nothrow)
void* _ZnajSt11align_val_tRKSt9nothrow_t(size_t size, size_t align, rp_nothrow_t t) RPALIAS(rpaligned_alloc_reverse_nothrow)
// 32-bit operators delete and delete[], sized and aligned
void _ZdlPvj(void* p, size_t n) RPALIAS(rpfree_size)
void _ZdaPvj(void* p, size_t n) RPALIAS(rpfree_size)
void _ZdlPvSt11align_val_t(void* p, size_t a) RPALIAS(rpfree_aligned)
void _ZdaPvSt11align_val_t(void* p, size_t a) RPALIAS(rpfree_aligned)
void _ZdlPvjSt11align_val_t(void* p, size_t n, size_t a) RPALIAS(rpfree_size_aligned)
void _ZdaPvjSt11align_val_t(void* p, size_t n, size_t a) RPALIAS(rpfree_size_aligned)
#endif
void* malloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1) RPALIAS(rpmalloc)
void* calloc(size_t count, size_t size) RPALIAS(rpcalloc)
void* realloc(void* ptr, size_t size) RPALIAS(rprealloc)
void* reallocf(void* ptr, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2) RPALIAS(rprealloc)
void* aligned_alloc(size_t alignment, size_t size) RPALIAS(rpaligned_alloc)
void* memalign(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2) RPALIAS(rpmemalign)
int posix_memalign(void** memptr, size_t alignment, size_t size) RPALIAS(rpposix_memalign)
void free(void* ptr) RPALIAS(rpfree)
void cfree(void* ptr) RPALIAS(rpfree)
#if defined(__ANDROID__) || defined(__FreeBSD__)
size_t malloc_usable_size(const void* ptr) RPALIAS(rpmalloc_usable_size)
#else
size_t malloc_usable_size(void* ptr) RPALIAS(rpmalloc_usable_size)
#endif
size_t malloc_size(void* ptr) RPALIAS(rpmalloc_usable_size)
#endif
static inline size_t
_rpmalloc_page_size(void) {
return _memory_page_size;
}
extern void* RPMALLOC_CDECL
reallocarray(void* ptr, size_t count, size_t size);
extern void* RPMALLOC_CDECL
reallocarray(void* ptr, size_t count, size_t size) {
size_t total;
#if ENABLE_VALIDATE_ARGS
#ifdef _MSC_VER
int err = SizeTMult(count, size, &total);
if ((err != S_OK) || (total >= MAX_ALLOC_SIZE)) {
errno = EINVAL;
return 0;
}
#else
int err = __builtin_umull_overflow(count, size, &total);
if (err || (total >= MAX_ALLOC_SIZE)) {
errno = EINVAL;
return 0;
}
#endif
#else
total = count * size;
#endif
return realloc(ptr, total);
}
extern inline void* RPMALLOC_CDECL
valloc(size_t size) {
get_thread_heap();
return rpaligned_alloc(_rpmalloc_page_size(), size);
}
extern inline void* RPMALLOC_CDECL
pvalloc(size_t size) {
get_thread_heap();
const size_t page_size = _rpmalloc_page_size();
const size_t aligned_size = ((size + page_size - 1) / page_size) * page_size;
#if ENABLE_VALIDATE_ARGS
if (aligned_size < size) {
errno = EINVAL;
return 0;
}
#endif
return rpaligned_alloc(_rpmalloc_page_size(), aligned_size);
}
#endif // ENABLE_OVERRIDE
#if ENABLE_PRELOAD
#ifdef _WIN32
#if defined(BUILD_DYNAMIC_LINK) && BUILD_DYNAMIC_LINK
extern __declspec(dllexport) BOOL WINAPI
DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved);
extern __declspec(dllexport) BOOL WINAPI
DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) {
(void)sizeof(reserved);
(void)sizeof(instance);
if (reason == DLL_PROCESS_ATTACH)
rpmalloc_initialize();
else if (reason == DLL_PROCESS_DETACH)
rpmalloc_finalize();
else if (reason == DLL_THREAD_ATTACH)
rpmalloc_thread_initialize();
else if (reason == DLL_THREAD_DETACH)
rpmalloc_thread_finalize(1);
return TRUE;
}
//end BUILD_DYNAMIC_LINK
#else
extern void
_global_rpmalloc_init(void) {
rpmalloc_set_main_thread();
rpmalloc_initialize();
}
#if defined(__clang__) || defined(__GNUC__)
static void __attribute__((constructor))
initializer(void) {
_global_rpmalloc_init();
}
#elif defined(_MSC_VER)
static int
_global_rpmalloc_xib(void) {
_global_rpmalloc_init();
return 0;
}
#pragma section(".CRT$XIB",read)
__declspec(allocate(".CRT$XIB")) void (*_rpmalloc_module_init)(void) = _global_rpmalloc_xib;
#pragma comment(linker, "/include:_rpmalloc_module_init")
#endif
//end !BUILD_DYNAMIC_LINK
#endif
#else
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
extern void
rpmalloc_set_main_thread(void);
static pthread_key_t destructor_key;
static void
thread_destructor(void*);
static void __attribute__((constructor))
initializer(void) {
rpmalloc_set_main_thread();
rpmalloc_initialize();
pthread_key_create(&destructor_key, thread_destructor);
}
static void __attribute__((destructor))
finalizer(void) {
rpmalloc_finalize();
}
typedef struct {
void* (*real_start)(void*);
void* real_arg;
} thread_starter_arg;
static void*
thread_starter(void* argptr) {
thread_starter_arg* arg = argptr;
void* (*real_start)(void*) = arg->real_start;
void* real_arg = arg->real_arg;
rpmalloc_thread_initialize();
rpfree(argptr);
pthread_setspecific(destructor_key, (void*)1);
return (*real_start)(real_arg);
}
static void
thread_destructor(void* value) {
(void)sizeof(value);
rpmalloc_thread_finalize(1);
}
#ifdef __APPLE__
static int
pthread_create_proxy(pthread_t* thread,
const pthread_attr_t* attr,
void* (*start_routine)(void*),
void* arg) {
rpmalloc_initialize();
thread_starter_arg* starter_arg = rpmalloc(sizeof(thread_starter_arg));
starter_arg->real_start = start_routine;
starter_arg->real_arg = arg;
return pthread_create(thread, attr, thread_starter, starter_arg);
}
MAC_INTERPOSE_SINGLE(pthread_create_proxy, pthread_create);
#else
#include <dlfcn.h>
int
pthread_create(pthread_t* thread,
const pthread_attr_t* attr,
void* (*start_routine)(void*),
void* arg) {
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || \
defined(__APPLE__) || defined(__HAIKU__)
char fname[] = "pthread_create";
#else
char fname[] = "_pthread_create";
#endif
void* real_pthread_create = dlsym(RTLD_NEXT, fname);
rpmalloc_thread_initialize();
thread_starter_arg* starter_arg = rpmalloc(sizeof(thread_starter_arg));
starter_arg->real_start = start_routine;
starter_arg->real_arg = arg;
return (*(int (*)(pthread_t*, const pthread_attr_t*, void* (*)(void*), void*))real_pthread_create)(thread, attr, thread_starter, starter_arg);
}
#endif
#endif
#endif
#if ENABLE_OVERRIDE
#if defined(__GLIBC__) && defined(__linux__)
void* __libc_malloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1) RPALIAS(rpmalloc)
void* __libc_calloc(size_t count, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(1, 2) RPALIAS(rpcalloc)
void* __libc_realloc(void* p, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2) RPALIAS(rprealloc)
void __libc_free(void* p) RPALIAS(rpfree)
void __libc_cfree(void* p) RPALIAS(rpfree)
void* __libc_memalign(size_t align, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2) RPALIAS(rpmemalign)
int __posix_memalign(void** p, size_t align, size_t size) RPALIAS(rpposix_memalign)
extern void* __libc_valloc(size_t size);
extern void* __libc_pvalloc(size_t size);
void*
__libc_valloc(size_t size) {
return valloc(size);
}
void*
__libc_pvalloc(size_t size) {
return pvalloc(size);
}
#endif
#endif
#if (defined(__GNUC__) || defined(__clang__))
#pragma GCC visibility pop
#endif

3638
source/extern/rpmalloc.c vendored Normal file

File diff suppressed because it is too large Load Diff

373
source/extern/rpmalloc.h vendored Normal file
View File

@ -0,0 +1,373 @@
/* rpmalloc.h - Memory allocator - Public Domain - 2016 Mattias Jansson
*
* This library provides a cross-platform lock free thread caching malloc implementation in C11.
* The latest source code is always available at
*
* https://github.com/mjansson/rpmalloc
*
* This library is put in the public domain; you can redistribute it and/or modify it without any restrictions.
*
*/
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__clang__) || defined(__GNUC__)
# define RPMALLOC_EXPORT __attribute__((visibility("default")))
# define RPMALLOC_ALLOCATOR
# if (defined(__clang_major__) && (__clang_major__ < 4)) || (defined(__GNUC__) && defined(ENABLE_PRELOAD) && ENABLE_PRELOAD)
# define RPMALLOC_ATTRIB_MALLOC
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size)
# else
# define RPMALLOC_ATTRIB_MALLOC __attribute__((__malloc__))
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size) __attribute__((alloc_size(size)))
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count, size) __attribute__((alloc_size(count, size)))
# endif
# define RPMALLOC_CDECL
#elif defined(_MSC_VER)
# define RPMALLOC_EXPORT
# define RPMALLOC_ALLOCATOR __declspec(allocator) __declspec(restrict)
# define RPMALLOC_ATTRIB_MALLOC
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
# define RPMALLOC_CDECL __cdecl
#else
# define RPMALLOC_EXPORT
# define RPMALLOC_ALLOCATOR
# define RPMALLOC_ATTRIB_MALLOC
# define RPMALLOC_ATTRIB_ALLOC_SIZE(size)
# define RPMALLOC_ATTRIB_ALLOC_SIZE2(count,size)
# define RPMALLOC_CDECL
#endif
//! Define RPMALLOC_CONFIGURABLE to enable configuring sizes. Will introduce
// a very small overhead due to some size calculations not being compile time constants
#ifndef RPMALLOC_CONFIGURABLE
#define RPMALLOC_CONFIGURABLE 0
#endif
//! Define RPMALLOC_FIRST_CLASS_HEAPS to enable heap based API (rpmalloc_heap_* functions).
// Will introduce a very small overhead to track fully allocated spans in heaps
#ifndef RPMALLOC_FIRST_CLASS_HEAPS
#define RPMALLOC_FIRST_CLASS_HEAPS 0
#endif
//! Flag to rpaligned_realloc to not preserve content in reallocation
#define RPMALLOC_NO_PRESERVE 1
//! Flag to rpaligned_realloc to fail and return null pointer if grow cannot be done in-place,
// in which case the original pointer is still valid (just like a call to realloc which failes to allocate
// a new block).
#define RPMALLOC_GROW_OR_FAIL 2
typedef struct rpmalloc_global_statistics_t {
//! Current amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
size_t mapped;
//! Peak amount of virtual memory mapped, all of which might not have been committed (only if ENABLE_STATISTICS=1)
size_t mapped_peak;
//! Current amount of memory in global caches for small and medium sizes (<32KiB)
size_t cached;
//! Current amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
size_t huge_alloc;
//! Peak amount of memory allocated in huge allocations, i.e larger than LARGE_SIZE_LIMIT which is 2MiB by default (only if ENABLE_STATISTICS=1)
size_t huge_alloc_peak;
//! Total amount of memory mapped since initialization (only if ENABLE_STATISTICS=1)
size_t mapped_total;
//! Total amount of memory unmapped since initialization (only if ENABLE_STATISTICS=1)
size_t unmapped_total;
} rpmalloc_global_statistics_t;
typedef struct rpmalloc_thread_statistics_t {
//! Current number of bytes available in thread size class caches for small and medium sizes (<32KiB)
size_t sizecache;
//! Current number of bytes available in thread span caches for small and medium sizes (<32KiB)
size_t spancache;
//! Total number of bytes transitioned from thread cache to global cache (only if ENABLE_STATISTICS=1)
size_t thread_to_global;
//! Total number of bytes transitioned from global cache to thread cache (only if ENABLE_STATISTICS=1)
size_t global_to_thread;
//! Per span count statistics (only if ENABLE_STATISTICS=1)
struct {
//! Currently used number of spans
size_t current;
//! High water mark of spans used
size_t peak;
//! Number of spans transitioned to global cache
size_t to_global;
//! Number of spans transitioned from global cache
size_t from_global;
//! Number of spans transitioned to thread cache
size_t to_cache;
//! Number of spans transitioned from thread cache
size_t from_cache;
//! Number of spans transitioned to reserved state
size_t to_reserved;
//! Number of spans transitioned from reserved state
size_t from_reserved;
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
size_t map_calls;
} span_use[64];
//! Per size class statistics (only if ENABLE_STATISTICS=1)
struct {
//! Current number of allocations
size_t alloc_current;
//! Peak number of allocations
size_t alloc_peak;
//! Total number of allocations
size_t alloc_total;
//! Total number of frees
size_t free_total;
//! Number of spans transitioned to cache
size_t spans_to_cache;
//! Number of spans transitioned from cache
size_t spans_from_cache;
//! Number of spans transitioned from reserved state
size_t spans_from_reserved;
//! Number of raw memory map calls (not hitting the reserve spans but resulting in actual OS mmap calls)
size_t map_calls;
} size_use[128];
} rpmalloc_thread_statistics_t;
typedef struct rpmalloc_config_t {
//! Map memory pages for the given number of bytes. The returned address MUST be
// aligned to the rpmalloc span size, which will always be a power of two.
// Optionally the function can store an alignment offset in the offset variable
// in case it performs alignment and the returned pointer is offset from the
// actual start of the memory region due to this alignment. The alignment offset
// will be passed to the memory unmap function. The alignment offset MUST NOT be
// larger than 65535 (storable in an uint16_t), if it is you must use natural
// alignment to shift it into 16 bits. If you set a memory_map function, you
// must also set a memory_unmap function or else the default implementation will
// be used for both. This function must be thread safe, it can be called by
// multiple threads simultaneously.
void* (*memory_map)(size_t size, size_t* offset);
//! Unmap the memory pages starting at address and spanning the given number of bytes.
// If release is set to non-zero, the unmap is for an entire span range as returned by
// a previous call to memory_map and that the entire range should be released. The
// release argument holds the size of the entire span range. If release is set to 0,
// the unmap is a partial decommit of a subset of the mapped memory range.
// If you set a memory_unmap function, you must also set a memory_map function or
// else the default implementation will be used for both. This function must be thread
// safe, it can be called by multiple threads simultaneously.
void (*memory_unmap)(void* address, size_t size, size_t offset, size_t release);
//! Called when an assert fails, if asserts are enabled. Will use the standard assert()
// if this is not set.
void (*error_callback)(const char* message);
//! Called when a call to map memory pages fails (out of memory). If this callback is
// not set or returns zero the library will return a null pointer in the allocation
// call. If this callback returns non-zero the map call will be retried. The argument
// passed is the number of bytes that was requested in the map call. Only used if
// the default system memory map function is used (memory_map callback is not set).
int (*map_fail_callback)(size_t size);
//! Size of memory pages. The page size MUST be a power of two. All memory mapping
// requests to memory_map will be made with size set to a multiple of the page size.
// Used if RPMALLOC_CONFIGURABLE is defined to 1, otherwise system page size is used.
size_t page_size;
//! Size of a span of memory blocks. MUST be a power of two, and in [4096,262144]
// range (unless 0 - set to 0 to use the default span size). Used if RPMALLOC_CONFIGURABLE
// is defined to 1.
size_t span_size;
//! Number of spans to map at each request to map new virtual memory blocks. This can
// be used to minimize the system call overhead at the cost of virtual memory address
// space. The extra mapped pages will not be written until actually used, so physical
// committed memory should not be affected in the default implementation. Will be
// aligned to a multiple of spans that match memory page size in case of huge pages.
size_t span_map_count;
//! Enable use of large/huge pages. If this flag is set to non-zero and page size is
// zero, the allocator will try to enable huge pages and auto detect the configuration.
// If this is set to non-zero and page_size is also non-zero, the allocator will
// assume huge pages have been configured and enabled prior to initializing the
// allocator.
// For Windows, see https://docs.microsoft.com/en-us/windows/desktop/memory/large-page-support
// For Linux, see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
int enable_huge_pages;
//! Respectively allocated pages and huge allocated pages names for systems
// supporting it to be able to distinguish among anonymous regions.
const char *page_name;
const char *huge_page_name;
} rpmalloc_config_t;
//! Initialize allocator with default configuration
RPMALLOC_EXPORT int
rpmalloc_initialize(void);
//! Initialize allocator with given configuration
RPMALLOC_EXPORT int
rpmalloc_initialize_config(const rpmalloc_config_t* config);
//! Get allocator configuration
RPMALLOC_EXPORT const rpmalloc_config_t*
rpmalloc_config(void);
//! Finalize allocator
RPMALLOC_EXPORT void
rpmalloc_finalize(void);
//! Initialize allocator for calling thread
RPMALLOC_EXPORT void
rpmalloc_thread_initialize(void);
//! Finalize allocator for calling thread
RPMALLOC_EXPORT void
rpmalloc_thread_finalize(int release_caches);
//! Perform deferred deallocations pending for the calling thread heap
RPMALLOC_EXPORT void
rpmalloc_thread_collect(void);
//! Query if allocator is initialized for calling thread
RPMALLOC_EXPORT int
rpmalloc_is_thread_initialized(void);
//! Get per-thread statistics
RPMALLOC_EXPORT void
rpmalloc_thread_statistics(rpmalloc_thread_statistics_t* stats);
//! Get global statistics
RPMALLOC_EXPORT void
rpmalloc_global_statistics(rpmalloc_global_statistics_t* stats);
//! Dump all statistics in human readable format to file (should be a FILE*)
RPMALLOC_EXPORT void
rpmalloc_dump_statistics(void* file);
//! Allocate a memory block of at least the given size
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc(size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(1);
//! Free the given memory block
RPMALLOC_EXPORT void
rpfree(void* ptr);
//! Allocate a memory block of at least the given size and zero initialize it
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpcalloc(size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(1, 2);
//! Reallocate the given block to at least the given size
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rprealloc(void* ptr, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Reallocate the given block to at least the given size and alignment,
// with optional control flags (see RPMALLOC_NO_PRESERVE).
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpaligned_realloc(void* ptr, size_t alignment, size_t size, size_t oldsize, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
//! Allocate a memory block of at least the given size and alignment.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpaligned_alloc(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Allocate a memory block of at least the given size and alignment, and zero initialize it.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpaligned_calloc(size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
//! Allocate a memory block of at least the given size and alignment.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmemalign(size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Allocate a memory block of at least the given size and alignment.
// Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB)
RPMALLOC_EXPORT int
rpposix_memalign(void** memptr, size_t alignment, size_t size);
//! Query the usable size of the given memory block (from given pointer to the end of block)
RPMALLOC_EXPORT size_t
rpmalloc_usable_size(void* ptr);
//! Dummy empty function for forcing linker symbol inclusion
RPMALLOC_EXPORT void
rpmalloc_linker_reference(void);
#if RPMALLOC_FIRST_CLASS_HEAPS
//! Heap type
typedef struct heap_t rpmalloc_heap_t;
//! Acquire a new heap. Will reuse existing released heaps or allocate memory for a new heap
// if none available. Heap API is implemented with the strict assumption that only one single
// thread will call heap functions for a given heap at any given time, no functions are thread safe.
RPMALLOC_EXPORT rpmalloc_heap_t*
rpmalloc_heap_acquire(void);
//! Release a heap (does NOT free the memory allocated by the heap, use rpmalloc_heap_free_all before destroying the heap).
// Releasing a heap will enable it to be reused by other threads. Safe to pass a null pointer.
RPMALLOC_EXPORT void
rpmalloc_heap_release(rpmalloc_heap_t* heap);
//! Allocate a memory block of at least the given size using the given heap.
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_alloc(rpmalloc_heap_t* heap, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(2);
//! Allocate a memory block of at least the given size using the given heap. The returned
// block will have the requested alignment. Alignment must be a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB).
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_aligned_alloc(rpmalloc_heap_t* heap, size_t alignment, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
//! Allocate a memory block of at least the given size using the given heap and zero initialize it.
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_calloc(rpmalloc_heap_t* heap, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
//! Allocate a memory block of at least the given size using the given heap and zero initialize it. The returned
// block will have the requested alignment. Alignment must either be zero, or a power of two and a multiple of sizeof(void*),
// and should ideally be less than memory page size. A caveat of rpmalloc
// internals is that this must also be strictly less than the span size (default 64KiB).
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_aligned_calloc(rpmalloc_heap_t* heap, size_t alignment, size_t num, size_t size) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE2(2, 3);
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
// by the same heap given to this function.
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_realloc(rpmalloc_heap_t* heap, void* ptr, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(3);
//! Reallocate the given block to at least the given size. The memory block MUST be allocated
// by the same heap given to this function. The returned block will have the requested alignment.
// Alignment must be either zero, or a power of two and a multiple of sizeof(void*), and should ideally be
// less than memory page size. A caveat of rpmalloc internals is that this must also be strictly less than
// the span size (default 64KiB).
RPMALLOC_EXPORT RPMALLOC_ALLOCATOR void*
rpmalloc_heap_aligned_realloc(rpmalloc_heap_t* heap, void* ptr, size_t alignment, size_t size, unsigned int flags) RPMALLOC_ATTRIB_MALLOC RPMALLOC_ATTRIB_ALLOC_SIZE(4);
//! Free the given memory block from the given heap. The memory block MUST be allocated
// by the same heap given to this function.
RPMALLOC_EXPORT void
rpmalloc_heap_free(rpmalloc_heap_t* heap, void* ptr);
//! Free all memory allocated by the heap
RPMALLOC_EXPORT void
rpmalloc_heap_free_all(rpmalloc_heap_t* heap);
//! Set the given heap as the current heap for the calling thread. A heap MUST only be current heap
// for a single thread, a heap can never be shared between multiple threads. The previous
// current heap for the calling thread is released to be reused by other threads.
RPMALLOC_EXPORT void
rpmalloc_heap_thread_set_current(rpmalloc_heap_t* heap);
//! Returns which heap the given pointer is allocated on
RPMALLOC_EXPORT rpmalloc_heap_t*
rpmalloc_get_heap_for_ptr(void* ptr);
#endif
#ifdef __cplusplus
}
#endif

111
source/extern/rpnew.h vendored Normal file
View File

@ -0,0 +1,111 @@
#ifdef __cplusplus
#include <new>
#include <rpmalloc.h>
#ifndef __CRTDECL
#define __CRTDECL
#endif
extern void __CRTDECL
operator delete(void* p) noexcept {
rpfree(p);
}
extern void __CRTDECL
operator delete[](void* p) noexcept {
rpfree(p);
}
extern void* __CRTDECL
operator new(std::size_t size) noexcept(false) {
return rpmalloc(size);
}
extern void* __CRTDECL
operator new[](std::size_t size) noexcept(false) {
return rpmalloc(size);
}
extern void* __CRTDECL
operator new(std::size_t size, const std::nothrow_t& tag) noexcept {
(void)sizeof(tag);
return rpmalloc(size);
}
extern void* __CRTDECL
operator new[](std::size_t size, const std::nothrow_t& tag) noexcept {
(void)sizeof(tag);
return rpmalloc(size);
}
#if (__cplusplus >= 201402L || _MSC_VER >= 1916)
extern void __CRTDECL
operator delete(void* p, std::size_t size) noexcept {
(void)sizeof(size);
rpfree(p);
}
extern void __CRTDECL
operator delete[](void* p, std::size_t size) noexcept {
(void)sizeof(size);
rpfree(p);
}
#endif
#if (__cplusplus > 201402L || defined(__cpp_aligned_new))
extern void __CRTDECL
operator delete(void* p, std::align_val_t align) noexcept {
(void)sizeof(align);
rpfree(p);
}
extern void __CRTDECL
operator delete[](void* p, std::align_val_t align) noexcept {
(void)sizeof(align);
rpfree(p);
}
extern void __CRTDECL
operator delete(void* p, std::size_t size, std::align_val_t align) noexcept {
(void)sizeof(size);
(void)sizeof(align);
rpfree(p);
}
extern void __CRTDECL
operator delete[](void* p, std::size_t size, std::align_val_t align) noexcept {
(void)sizeof(size);
(void)sizeof(align);
rpfree(p);
}
extern void* __CRTDECL
operator new(std::size_t size, std::align_val_t align) noexcept(false) {
return rpaligned_alloc(static_cast<size_t>(align), size);
}
extern void* __CRTDECL
operator new[](std::size_t size, std::align_val_t align) noexcept(false) {
return rpaligned_alloc(static_cast<size_t>(align), size);
}
extern void* __CRTDECL
operator new(std::size_t size, std::align_val_t align, const std::nothrow_t& tag) noexcept {
(void)sizeof(tag);
return rpaligned_alloc(static_cast<size_t>(align), size);
}
extern void* __CRTDECL
operator new[](std::size_t size, std::align_val_t align, const std::nothrow_t& tag) noexcept {
(void)sizeof(tag);
return rpaligned_alloc(static_cast<size_t>(align), size);
}
#endif
#endif

View File

@ -81,7 +81,7 @@ void BaseScriptPane::tick(float dt) {
}
for (auto p : m_canvasKeyCallbacks) {
for (auto const& keyEvent : p.first->pullKeyEvents())
m_script.invoke(p.second, (int)keyEvent.key, keyEvent.keyDown);
m_script.invoke(p.second, (int)keyEvent.key, keyEvent.keyDown, KeyNames.getRight(keyEvent.key));
}
m_script.update(m_script.updateDt(dt));

View File

@ -77,14 +77,15 @@ void CharSelectionPane::updateCharacterPlates() {
auto updatePlayerLine = [this](String name, unsigned scrollPosition) {
auto charSelector = fetchChild<LargeCharPlateWidget>(name);
if (auto playerUuid = m_playerStorage->playerUuidAt(scrollPosition)) {
auto player = m_playerStorage->loadPlayer(*playerUuid);
player->humanoid()->setFacingDirection(Direction::Right);
charSelector->setPlayer(player);
charSelector->enableDelete([this, playerUuid](Widget*) { m_deleteCallback(*playerUuid); });
} else {
charSelector->setPlayer(PlayerPtr());
charSelector->disableDelete();
if (auto player = m_playerStorage->loadPlayer(*playerUuid)) {
player->humanoid()->setFacingDirection(Direction::Right);
charSelector->setPlayer(player);
charSelector->enableDelete([this, playerUuid](Widget*) { m_deleteCallback(*playerUuid); });
return;
}
}
charSelector->setPlayer(PlayerPtr());
charSelector->disableDelete();
};
updatePlayerLine("charSelector1", m_downScroll + 0);

View File

@ -15,9 +15,9 @@
namespace Star {
ClientCommandProcessor::ClientCommandProcessor(UniverseClientPtr universeClient, CinematicPtr cinematicOverlay,
MainInterfacePaneManager* paneManager, StringMap<StringList> macroCommands)
MainInterfacePaneManager* paneManager, StringMap<StringList> macroCommands)
: m_universeClient(std::move(universeClient)), m_cinematicOverlay(std::move(cinematicOverlay)),
m_paneManager(paneManager), m_macroCommands(std::move(macroCommands)) {
m_paneManager(paneManager), m_macroCommands(std::move(macroCommands)) {
m_builtinCommands = {
{"reload", bind(&ClientCommandProcessor::reload, this)},
{"whoami", bind(&ClientCommandProcessor::whoami, this)},
@ -51,7 +51,8 @@ ClientCommandProcessor::ClientCommandProcessor(UniverseClientPtr universeClient,
{"maketechavailable", bind(&ClientCommandProcessor::makeTechAvailable, this, _1)},
{"enabletech", bind(&ClientCommandProcessor::enableTech, this, _1)},
{"upgradeship", bind(&ClientCommandProcessor::upgradeShip, this, _1)},
{"swap", bind(&ClientCommandProcessor::swap, this, _1)}
{"swap", bind(&ClientCommandProcessor::swap, this, _1)},
{"respawnInWorld", bind(&ClientCommandProcessor::respawnInWorld, this, _1)}
};
}
@ -91,9 +92,12 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
}
} else {
auto player = m_universeClient->mainPlayer();
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), "/" + command, { allArguments }))
result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true));
else
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), "/" + command, {allArguments})) {
if (messageResult->isType(Json::Type::String))
result.append(*messageResult->stringPtr());
else if (!messageResult->isNull())
result.append(messageResult->repr(1, true));
} else
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast);
}
return result;
@ -125,7 +129,7 @@ String ClientCommandProcessor::reload() {
String ClientCommandProcessor::whoami() {
return strf("Client: You are {}. You are {}an Admin.",
m_universeClient->mainPlayer()->name(), m_universeClient->mainPlayer()->isAdmin() ? "" : "not ");
m_universeClient->mainPlayer()->name(), m_universeClient->mainPlayer()->isAdmin() ? "" : "not ");
}
String ClientCommandProcessor::gravity() {
@ -183,7 +187,7 @@ String ClientCommandProcessor::setGravity(String const& argumentsString) {
return "You must be an admin to use this command.";
m_universeClient->worldClient()->overrideGravity(lexicalCast<float>(arguments.at(0)));
return strf("Gravity set to {}, the change is LOCAL ONLY", arguments.at(0));
return strf("Gravity set to {} (This is client-side!)", arguments.at(0));
}
String ClientCommandProcessor::resetGravity() {
@ -270,8 +274,8 @@ String ClientCommandProcessor::previewNewQuest(String const& argumentsString) {
return "You must be an admin to use this command.";
return previewQuestPane(arguments, [this](QuestPtr const& quest) {
return make_shared<NewQuestInterface>(m_universeClient->questManager(), quest, m_universeClient->mainPlayer());
});
return make_shared<NewQuestInterface>(m_universeClient->questManager(), quest, m_universeClient->mainPlayer());
});
}
String ClientCommandProcessor::previewQuestComplete(String const& argumentsString) {
@ -280,8 +284,8 @@ String ClientCommandProcessor::previewQuestComplete(String const& argumentsStrin
return "You must be an admin to use this command.";
return previewQuestPane(arguments, [this](QuestPtr const& quest) {
return make_shared<QuestCompleteInterface>(quest, m_universeClient->mainPlayer(), CinematicPtr{});
});
return make_shared<QuestCompleteInterface>(quest, m_universeClient->mainPlayer(), CinematicPtr{});
});
}
String ClientCommandProcessor::previewQuestFailed(String const& argumentsString) {
@ -290,8 +294,8 @@ String ClientCommandProcessor::previewQuestFailed(String const& argumentsString)
return "You must be an admin to use this command.";
return previewQuestPane(arguments, [this](QuestPtr const& quest) {
return make_shared<QuestFailedInterface>(quest, m_universeClient->mainPlayer());
});
return make_shared<QuestFailedInterface>(quest, m_universeClient->mainPlayer());
});
}
String ClientCommandProcessor::clearScannedObjects() {
@ -424,4 +428,16 @@ String ClientCommandProcessor::swap(String const& argumentsString) {
return "Failed to swap player";
}
String ClientCommandProcessor::respawnInWorld(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
auto worldClient = m_universeClient->worldClient();
if (arguments.size() == 0)
return strf("Respawn in this world is currently {}", worldClient->respawnInWorld() ? "true" : "false");
bool respawnInWorld = Json::parse(arguments.at(0)).toBool();
worldClient->setRespawnInWorld(respawnInWorld);
return strf("Respawn in this world set to {} (This is client-side!)", respawnInWorld ? "true" : "false");
}
}

View File

@ -58,6 +58,7 @@ private:
String enableTech(String const& argumentsString);
String upgradeShip(String const& argumentsString);
String swap(String const& argumentsString);
String respawnInWorld(String const& argumentsString);
UniverseClientPtr m_universeClient;
CinematicPtr m_cinematicOverlay;

View File

@ -97,10 +97,10 @@ GraphicsMenu::GraphicsMenu() {
Root::singleton().configuration()->set("monochromeLighting", checked);
syncGui();
});
reader.registerCallback("objectLightingCheckbox", [=](Widget*) {
bool checked = fetchChild<ButtonWidget>("objectLightingCheckbox")->isChecked();
m_localChanges.set("newObjectLighting", checked);
Root::singleton().configuration()->set("newObjectLighting", checked);
reader.registerCallback("newLightingCheckbox", [=](Widget*) {
bool checked = fetchChild<ButtonWidget>("newLightingCheckbox")->isChecked();
m_localChanges.set("newLighting", checked);
Root::singleton().configuration()->set("newLighting", checked);
syncGui();
});
@ -162,7 +162,7 @@ StringList const GraphicsMenu::ConfigKeys = {
"antiAliasing",
"hardwareCursor",
"monochromeLighting",
"newObjectLighting"
"newLighting"
};
void GraphicsMenu::initConfig() {
@ -229,7 +229,7 @@ void GraphicsMenu::syncGui() {
fetchChild<ButtonWidget>("multiTextureCheckbox")->setChecked(m_localChanges.get("useMultiTexturing").optBool().value(true));
fetchChild<ButtonWidget>("antiAliasingCheckbox")->setChecked(m_localChanges.get("antiAliasing").toBool());
fetchChild<ButtonWidget>("monochromeCheckbox")->setChecked(m_localChanges.get("monochromeLighting").toBool());
fetchChild<ButtonWidget>("objectLightingCheckbox")->setChecked(m_localChanges.get("newObjectLighting").optBool().value(true));
fetchChild<ButtonWidget>("newLightingCheckbox")->setChecked(m_localChanges.get("newLighting").optBool().value(true));
fetchChild<ButtonWidget>("hardwareCursorCheckbox")->setChecked(m_localChanges.get("hardwareCursor").toBool());
}

View File

@ -238,7 +238,8 @@ PanePtr InventoryPane::createTooltip(Vec2I const& screenPosition) {
if (auto techIcon = fetchChild<ImageWidget>(strf("tech{}", p.second))) {
if (techIcon->screenBoundRect().contains(screenPosition)) {
if (auto techModule = m_player->techs()->equippedTechs().maybe(p.first))
return SimpleTooltipBuilder::buildTooltip(techDatabase->tech(*techModule).description);
if (techDatabase->contains(*techModule))
return SimpleTooltipBuilder::buildTooltip(techDatabase->tech(*techModule).description);
}
}
}
@ -325,10 +326,13 @@ void InventoryPane::update(float dt) {
auto techDatabase = Root::singleton().techDatabase();
for (auto const& p : TechTypeNames) {
if (auto techIcon = fetchChild<ImageWidget>(strf("tech{}", p.second))) {
if (auto techModule = m_player->techs()->equippedTechs().maybe(p.first))
techIcon->setImage(techDatabase->tech(*techModule).icon);
else
techIcon->setImage("");
if (auto techModule = m_player->techs()->equippedTechs().maybe(p.first)) {
if (techDatabase->contains(*techModule)) {
techIcon->setImage(techDatabase->tech(*techModule).icon);
continue;
}
}
techIcon->setImage("");
}
}

View File

@ -760,23 +760,30 @@ void MainInterface::update(float dt) {
m_chatBubbleManager->setCamera(m_worldPainter->camera());
if (auto worldClient = m_client->worldClient()) {
auto chatActions = worldClient->pullPendingChatActions();
auto portraitActions = chatActions.filtered([](ChatAction action) { return action.is<PortraitChatAction>(); });
for (auto action : portraitActions) {
PortraitChatAction portraitAction = action.get<PortraitChatAction>();
for (auto& action : chatActions) {
if (action.is<PortraitChatAction>()) {
PortraitChatAction& portraitAction = action.get<PortraitChatAction>();
String name;
if (auto npc = as<Npc>(worldClient->entity(portraitAction.entity)))
name = npc->name();
String name;
if (auto npc = as<Npc>(worldClient->entity(portraitAction.entity)))
name = npc->name();
ChatReceivedMessage message = {
{ MessageContext::World },
ServerConnectionId,
Text::stripEscapeCodes(name),
Text::stripEscapeCodes(portraitAction.text),
Text::stripEscapeCodes(portraitAction.portrait.replace("<frame>", "0"))
};
m_chat->addMessages({message}, false);
ChatReceivedMessage message = {
{MessageContext::World},
ServerConnectionId,
Text::stripEscapeCodes(name),
Text::stripEscapeCodes(portraitAction.text),
Text::stripEscapeCodes(portraitAction.portrait.replace("<frame>", "0"))};
m_chat->addMessages({message}, false);
} else if (action.is<SayChatAction>()) {
SayChatAction& sayAction = action.get<SayChatAction>();
if (sayAction.config) {
if (auto message = sayAction.config.opt("message"))
m_chat->addMessages({ChatReceivedMessage(*message)}, sayAction.config.getBool("showPane", false));
}
}
}
m_chatBubbleManager->addChatActions(chatActions);

View File

@ -211,14 +211,26 @@ void Voice::loadJson(Json const& config, bool skipSave) {
m_lastInputTime = 0;
}
bool shouldResetEncoder = false;
if (auto channelMode = config.optString("channelMode")) {
if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode), changed)) {
closeDevice();
resetEncoder();
shouldResetEncoder = true;
resetDevice();
}
}
// not saving this setting to disk, as it's just for audiophiles
// don't want someone fudging their bitrate from the intended defaults and forgetting
if (auto bitrate = config.opt("bitrate")) {
unsigned newBitrate = bitrate->canConvert(Json::Type::Int)
? clamp((unsigned)bitrate->toUInt(), 6000u, 510000u) : 0;
shouldResetEncoder |= change(m_bitrate, newBitrate, changed);
}
if (shouldResetEncoder)
resetEncoder();
if (changed && !skipSave)
scheduleSave();
}
@ -607,7 +619,8 @@ void Voice::resetEncoder() {
int channels = encoderChannels();
MutexLocker locker(m_threadMutex);
m_encoder.reset(createEncoder(channels));
opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000));
int bitrate = m_bitrate > 0 ? (int)m_bitrate : (channels == 2 ? 50000 : 24000);
opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(bitrate));
}
void Voice::resetDevice() {

View File

@ -195,6 +195,7 @@ private:
Maybe<String> m_deviceName;
VoiceInputMode m_inputMode;
VoiceChannelMode m_channelMode;
unsigned m_bitrate = 0;
ThreadFunction<void> m_thread;
Mutex m_threadMutex;

View File

@ -55,7 +55,7 @@ ChatReceivedMessage::ChatReceivedMessage(Json const& json) : ChatReceivedMessage
fromConnection = json.getUInt("fromConnection", 0);
fromNick = json.getString("fromNick", "");
portrait = json.getString("portrait", "");
text = json.getString("text");
text = json.getString("text", "");
}
Json ChatReceivedMessage::toJson() const {

View File

@ -53,12 +53,14 @@ String CommandProcessor::help(ConnectionId connectionId, String const& argumentS
auto assets = Root::singleton().assets();
auto basicCommands = assets->json("/help.config:basicCommands");
auto openSbCommands = assets->json("/help.config:openSbCommands");
auto adminCommands = assets->json("/help.config:adminCommands");
auto debugCommands = assets->json("/help.config:debugCommands");
auto openSbDebugCommands = assets->json("/help.config:openSbDebugCommands");
if (arguments.size()) {
if (arguments.size() >= 1) {
if (auto helpText = basicCommands.optString(arguments[0]).orMaybe(adminCommands.optString(arguments[0])).orMaybe(debugCommands.optString(arguments[0])))
if (auto helpText = basicCommands.optString(arguments[0]).orMaybe(openSbCommands.optString(arguments[0])).orMaybe(adminCommands.optString(arguments[0])).orMaybe(debugCommands.optString(arguments[0])).orMaybe(openSbDebugCommands.optString(arguments[0])))
return *helpText;
}
}
@ -74,12 +76,18 @@ String CommandProcessor::help(ConnectionId connectionId, String const& argumentS
String basicHelpFormat = assets->json("/help.config:basicHelpText").toString();
res = res + strf(basicHelpFormat.utf8Ptr(), commandDescriptions(basicCommands));
String openSbHelpFormat = assets->json("/help.config:openSbHelpText").toString();
res = res + "\n" + strf(openSbHelpFormat.utf8Ptr(), commandDescriptions(openSbCommands));
if (!adminCheck(connectionId, "")) {
String adminHelpFormat = assets->json("/help.config:adminHelpText").toString();
res = res + "\n" + strf(adminHelpFormat.utf8Ptr(), commandDescriptions(adminCommands));
String debugHelpFormat = assets->json("/help.config:debugHelpText").toString();
res = res + "\n" + strf(debugHelpFormat.utf8Ptr(), commandDescriptions(debugCommands));
String openSbDebugHelpFormat = assets->json("/help.config:openSbDebugHelpText").toString();
res = res + "\n" + strf(openSbDebugHelpFormat.utf8Ptr(), commandDescriptions(openSbDebugCommands));
}
res = res + "\n" + basicCommands.getString("help");
@ -209,7 +217,7 @@ String CommandProcessor::timewarp(ConnectionId connectionId, String const& argum
return "Great Scott! We can't go back in time!";
m_universe->universeClock()->adjustTime(time);
return strf("It's just a jump to the {}...", time > 0.0 ? "left" : "right");
return time > 0.0 ? "It's just a jump to the left..." : "And then a step to the right...";
} catch (BadLexicalCast const&) {
return strf("Could not parse the argument {} as a time adjustment", arguments[0]);
}

View File

@ -654,12 +654,10 @@ namespace Dungeon {
});
ground[1] = max(ground[1], liquid[1]);
if (air.y() < ground.y())
throw DungeonException("Invalid ground vs air contraint. Ground at: " + toString(ground.y()) + " Air at: "
+ toString(air.y())
+ " Pixels: highest ground:"
+ toString(ground)
+ " lowest air:"
+ toString(air));
throw DungeonException::format(
"Invalid ground vs air contraint! Ground {} can't be above air {}"
" (try moving your 'require there be air here' anchors above any other 'require there be (something) here' anchors.)",
ground, air);
return air.y();
}

View File

@ -122,6 +122,22 @@ Json Input::inputEventToJson(InputEvent const& input) {
{"mouseMove", jsonFromVec2I(mouseMove->mouseMove)},
{"mousePosition", jsonFromVec2I(mouseMove->mousePosition)}
};
} else if (auto controllerDown = input.ptr<ControllerButtonDownEvent>()) {
type = "ControllerButtonDown";
data = JsonObject{
{"controllerButton", ControllerButtonNames.getRight(controllerDown->controllerButton)},
{"controller", controllerDown->controller}};
} else if (auto controllerUp = input.ptr<ControllerButtonUpEvent>()) {
type = "ControllerButtonUp";
data = JsonObject{
{"controllerButton", ControllerButtonNames.getRight(controllerUp->controllerButton)},
{"controller", controllerUp->controller}};
} else if (auto controllerAxis = input.ptr<ControllerAxisEvent>()) {
type = "ControllerAxis";
data = JsonObject{
{"controllerAxis", ControllerAxisNames.getRight(controllerAxis->controllerAxis)},
{"controllerAxisValue", controllerAxis->controllerAxisValue},
{"controller", controllerAxis->controller}};
}
if (data) {
@ -144,19 +160,28 @@ Input::Bind Input::bindFromJson(Json const& json) {
if (type == "key") {
KeyBind keyBind;
keyBind.key = KeyNames.getLeft(value.toString());
if (auto key = KeyNames.maybeLeft(value.toString()))
keyBind.key = *key;
else
return bind;
keyBind.mods = keyModsFromJson(json.getArray("mods", {}), &keyBind.priority);
bind = std::move(keyBind);
}
else if (type == "mouse") {
MouseBind mouseBind;
mouseBind.button = MouseButtonNames.getLeft(value.toString());
if (auto button = MouseButtonNames.maybeLeft(value.toString()))
mouseBind.button = *button;
else
return bind;
mouseBind.mods = keyModsFromJson(json.getArray("mods", {}), &mouseBind.priority);
bind = std::move(mouseBind);
}
else if (type == "controller") {
ControllerBind controllerBind;
controllerBind.button = ControllerButtonNames.getLeft(value.toString());
if (auto button = ControllerButtonNames.maybeLeft(value.toString()))
controllerBind.button = *button;
else
return bind;
controllerBind.controller = json.getUInt("controller", 0);
bind = std::move(controllerBind);
}
@ -186,7 +211,8 @@ Json Input::bindToJson(Bind const& bind) {
else if (auto controllerBind = bind.ptr<ControllerBind>()) {
return JsonObject{
{"type", "controller"},
{"value", ControllerButtonNames.getRight(controllerBind->button)}
{"value", ControllerButtonNames.getRight(controllerBind->button)},
{"controller", controllerBind->controller}
};
}
@ -392,7 +418,8 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
m_bindStates[bind].press();
}
}
} else if (auto keyUp = input.ptr<KeyUpEvent>()) {
}
else if (auto keyUp = input.ptr<KeyUpEvent>()) {
auto keyToMod = KeysToMods.rightPtr(keyUp->key);
if (keyToMod)
m_pressedMods &= ~*keyToMod;
@ -410,7 +437,8 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
state->release();
}
}
} else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
}
else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
m_mousePosition = mouseDown->mousePosition;
if (!gameProcessed) {
auto& state = m_mouseStates[mouseDown->mouseButton];
@ -422,7 +450,8 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
m_bindStates[bind].press();
}
}
} else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
}
else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
m_mousePosition = mouseUp->mousePosition;
if (auto state = m_mouseStates.ptr(mouseUp->mouseButton)) {
state->releasePositions.append(mouseUp->mousePosition);
@ -439,6 +468,28 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
else if (auto mouseMove = input.ptr<MouseMoveEvent>()) {
m_mousePosition = mouseMove->mousePosition;
}
else if (auto controllerDown = input.ptr<ControllerButtonDownEvent>()) {
if (!gameProcessed) {
auto& state = m_controllerStates[controllerDown->controllerButton];
state.press();
if (auto binds = m_bindMappings.ptr(controllerDown->controllerButton)) {
for (auto bind : filterBindEntries(*binds, m_pressedMods))
m_bindStates[bind].press();
}
}
}
else if (auto controllerUp = input.ptr<ControllerButtonUpEvent>()) {
if (auto state = m_controllerStates.ptr(controllerUp->controllerButton))
state->release();
if (auto binds = m_bindMappings.ptr(controllerUp->controllerButton)) {
for (auto& bind : *binds) {
if (auto state = m_bindStates.ptr(bind.entry))
state->release();
}
}
}
return false;
}

View File

@ -80,7 +80,7 @@ MaterialRenderProfile parseMaterialRenderProfile(Json const& spec, String const&
bool lightTransparent = spec.getBool("lightTransparent", false);
profile.foregroundLightTransparent = spec.getBool("foregroundLightTransparent", lightTransparent);
profile.backgroundLightTransparent = spec.getBool("backgroundLightTransparent", lightTransparent);
profile.colorVariants = spec.getBool("multiColored", false) ? spec.getUInt("colorVariants", MaxMaterialColorVariant) : 0;
profile.colorVariants = spec.getBool("multiColored", false) ? spec.getUInt("colorVariants", (uint64_t)MaxMaterialColorVariant + 1) : 0;
for (auto& entry : spec.getArray("colorDirectives", JsonArray()))
profile.colorDirectives.append(entry.toString());
profile.occludesBehind = spec.getBool("occludesBelow", true);
@ -128,14 +128,12 @@ MaterialRenderProfile parseMaterialRenderProfile(Json const& spec, String const&
auto flipTextureCoordinates = [imageHeight](
RectF const& rect) { return RectF::withSize(Vec2F(rect.xMin(), imageHeight - rect.yMax()), rect.size()); };
for (unsigned v = 0; v < variants; ++v) {
if (profile.colorVariants > 0) {
for (MaterialColorVariant c = 0; c <= profile.colorVariants; ++c) {
RectF textureRect = RectF::withSize(texturePosition + variantStride * v + colorStride * c, textureSize);
renderPiece->variants[c].append(flipTextureCoordinates(textureRect));
}
} else {
RectF textureRect = RectF::withSize(texturePosition + variantStride * v, textureSize);
renderPiece->variants[DefaultMaterialColorVariant].append(flipTextureCoordinates(textureRect));
auto i = DefaultMaterialColorVariant;
RectF textureRect = RectF::withSize(texturePosition + variantStride * v, textureSize);
renderPiece->variants[i].append(flipTextureCoordinates(textureRect));
for (MaterialColorVariant c = 0; c != profile.colorVariants; ++c) {
RectF textureRect = RectF::withSize(texturePosition + variantStride * v + colorStride * ++i, textureSize);
renderPiece->variants[i].append(flipTextureCoordinates(textureRect));
}
}

View File

@ -66,6 +66,9 @@ Maybe<PacketStats> PacketSocket::outgoingStats() const {
void PacketSocket::setLegacy(bool legacy) { m_legacy = legacy; }
bool PacketSocket::legacy() const { return m_legacy; }
void CompressedPacketSocket::setCompressionStreamEnabled(bool enabled) { m_useCompressionStream = enabled; }
bool CompressedPacketSocket::compressionStreamEnabled() const { return m_useCompressionStream; }
pair<LocalPacketSocketUPtr, LocalPacketSocketUPtr> LocalPacketSocket::openPair() {
auto lhsIncomingPipe = make_shared<Pipe>();
auto rhsIncomingPipe = make_shared<Pipe>();
@ -146,7 +149,7 @@ void TcpPacketSocket::close() {
void TcpPacketSocket::sendPackets(List<PacketPtr> packets) {
auto it = makeSMutableIterator(packets);
if (m_useCompressionStream) {
if (compressionStreamEnabled()) {
DataStreamBuffer outBuffer;
while (it.hasNext()) {
PacketPtr& packet = it.next();
@ -233,7 +236,7 @@ List<PacketPtr> TcpPacketSocket::receivePackets() {
if (packetSize > ds.remaining())
break;
m_incomingStats.mix(packetType, packetSize, !m_useCompressionStream);
m_incomingStats.mix(packetType, packetSize, !compressionStreamEnabled());
DataStreamExternalBuffer packetStream(ds.ptr() + ds.pos(), packetSize);
ByteArray uncompressed;
@ -280,17 +283,17 @@ bool TcpPacketSocket::writeData() {
bool dataSent = false;
try {
if (!m_outputBuffer.empty()) {
if (m_useCompressionStream) {
auto compressed = m_compressionStream.compress(m_outputBuffer);
if (compressionStreamEnabled()) {
auto compressedBuffer = m_compressionStream.compress(m_outputBuffer);
m_outputBuffer.clear();
m_compressedBuffer.append(compressed.ptr(), compressed.size());
size_t written = m_socket->send(m_compressedBuffer.ptr(), m_compressedBuffer.size());
if (written > 0) {
dataSent = true;
m_compressedBuffer.trimLeft(written);
m_outgoingStats.mix(written);
}
do {
size_t written = m_socket->send(compressedBuffer.ptr(), compressedBuffer.size());
if (written > 0) {
dataSent = true;
compressedBuffer.trimLeft(written);
m_outgoingStats.mix(written);
}
} while (!compressedBuffer.empty());
} else {
do {
size_t written = m_socket->send(m_outputBuffer.ptr(), m_outputBuffer.size());
@ -319,10 +322,10 @@ bool TcpPacketSocket::readData() {
if (readAmount == 0)
break;
dataReceived = true;
if (m_useCompressionStream) {
if (compressionStreamEnabled()) {
m_incomingStats.mix(readAmount);
auto decompressed = m_decompressionStream.decompress(readBuffer, readAmount);
m_inputBuffer.append(decompressed.ptr(), decompressed.size());
m_inputBuffer.append(decompressed);
} else {
m_inputBuffer.append(readBuffer, readAmount);
}
@ -345,7 +348,6 @@ Maybe<PacketStats> TcpPacketSocket::outgoingStats() const {
}
void TcpPacketSocket::setLegacy(bool legacy) {
m_useCompressionStream = !legacy;
PacketSocket::setLegacy(legacy);
}
@ -366,43 +368,58 @@ void P2PPacketSocket::close() {
void P2PPacketSocket::sendPackets(List<PacketPtr> packets) {
auto it = makeSMutableIterator(packets);
while (it.hasNext()) {
PacketType currentType = it.peekNext()->type();
PacketCompressionMode currentCompressionMode = it.peekNext()->compressionMode();
DataStreamBuffer packetBuffer;
while (it.hasNext()
&& it.peekNext()->type() == currentType
&& it.peekNext()->compressionMode() == currentCompressionMode) {
if (legacy())
it.next()->writeLegacy(packetBuffer);
else
it.next()->write(packetBuffer);
}
// Packets must read and write actual data, because this is used to
// determine packet count
starAssert(!packetBuffer.empty());
ByteArray compressedPackets;
bool mustCompress = currentCompressionMode == PacketCompressionMode::Enabled;
bool perhapsCompress = currentCompressionMode == PacketCompressionMode::Automatic && packetBuffer.size() > 64;
if (mustCompress || perhapsCompress)
compressedPackets = compressData(packetBuffer.data());
if (compressionStreamEnabled()) {
DataStreamBuffer outBuffer;
outBuffer.write(currentType);
if (!compressedPackets.empty() && (mustCompress || compressedPackets.size() < packetBuffer.size())) {
outBuffer.write<bool>(true);
outBuffer.writeData(compressedPackets.ptr(), compressedPackets.size());
m_outgoingStats.mix(currentType, compressedPackets.size());
} else {
while (it.hasNext()) {
PacketType currentType = it.peekNext()->type();
DataStreamBuffer packetBuffer;
while (it.hasNext() && it.peekNext()->type() == currentType)
it.next()->write(packetBuffer);
outBuffer.write(currentType);
outBuffer.write<bool>(false);
outBuffer.writeData(packetBuffer.ptr(), packetBuffer.size());
m_outgoingStats.mix(currentType, packetBuffer.size());
m_outgoingStats.mix(currentType, packetBuffer.size(), false);
m_outputMessages.append(m_compressionStream.compress(outBuffer.takeData()));
}
} else {
while (it.hasNext()) {
PacketType currentType = it.peekNext()->type();
PacketCompressionMode currentCompressionMode = it.peekNext()->compressionMode();
DataStreamBuffer packetBuffer;
while (it.hasNext()
&& it.peekNext()->type() == currentType
&& it.peekNext()->compressionMode() == currentCompressionMode) {
if (legacy())
it.next()->writeLegacy(packetBuffer);
else
it.next()->write(packetBuffer);
}
// Packets must read and write actual data, because this is used to
// determine packet count
starAssert(!packetBuffer.empty());
ByteArray compressedPackets;
bool mustCompress = currentCompressionMode == PacketCompressionMode::Enabled;
bool perhapsCompress = currentCompressionMode == PacketCompressionMode::Automatic && packetBuffer.size() > 64;
if (mustCompress || perhapsCompress)
compressedPackets = compressData(packetBuffer.data());
DataStreamBuffer outBuffer;
outBuffer.write(currentType);
if (!compressedPackets.empty() && (mustCompress || compressedPackets.size() < packetBuffer.size())) {
outBuffer.write<bool>(true);
outBuffer.writeData(compressedPackets.ptr(), compressedPackets.size());
m_outgoingStats.mix(currentType, compressedPackets.size());
} else {
outBuffer.write<bool>(false);
outBuffer.writeData(packetBuffer.ptr(), packetBuffer.size());
m_outgoingStats.mix(currentType, packetBuffer.size());
}
m_outputMessages.append(outBuffer.takeData());
}
m_outputMessages.append(outBuffer.takeData());
}
}
@ -420,9 +437,9 @@ List<PacketPtr> P2PPacketSocket::receivePackets() {
if (packetCompressed)
packetBytes = uncompressData(packetBytes);
m_incomingStats.mix(packetType, packetSize);
m_incomingStats.mix(packetType, packetSize, !compressionStreamEnabled());
DataStreamBuffer packetStream(std::move(packetBytes));
DataStreamExternalBuffer packetStream(packetBytes);
do {
PacketPtr packet = createPacket(packetType);
packet->setCompressionMode(packetCompressed ? PacketCompressionMode::Enabled : PacketCompressionMode::Disabled);
@ -450,6 +467,7 @@ bool P2PPacketSocket::writeData() {
if (m_socket) {
while (!m_outputMessages.empty()) {
if (m_socket->sendMessage(m_outputMessages.first())) {
m_outgoingStats.mix(m_outputMessages.first().size());
m_outputMessages.removeFirst();
workDone = true;
} else {
@ -466,7 +484,10 @@ bool P2PPacketSocket::readData() {
if (m_socket) {
while (auto message = m_socket->receiveMessage()) {
m_inputMessages.append(message.take());
m_incomingStats.mix(message->size());
m_inputMessages.append(compressionStreamEnabled()
? m_decompressionStream.decompress(*message)
: *message);
workDone = true;
}
}

View File

@ -78,7 +78,20 @@ public:
virtual void setLegacy(bool legacy);
virtual bool legacy() const;
private:
bool m_legacy = true;
bool m_legacy = false;
};
class CompressedPacketSocket : public PacketSocket {
public:
virtual ~CompressedPacketSocket() = default;
virtual void setCompressionStreamEnabled(bool enabled);
virtual bool compressionStreamEnabled() const;
private:
bool m_useCompressionStream = false;
protected:
CompressionStream m_compressionStream;
DecompressionStream m_decompressionStream;
};
// PacketSocket for local communication.
@ -112,7 +125,7 @@ private:
};
// Wraps a TCP socket into a PacketSocket.
class TcpPacketSocket : public PacketSocket {
class TcpPacketSocket : public CompressedPacketSocket {
public:
static TcpPacketSocketUPtr open(TcpSocketPtr socket);
@ -140,14 +153,10 @@ private:
PacketStatCollector m_outgoingStats;
ByteArray m_outputBuffer;
ByteArray m_inputBuffer;
bool m_useCompressionStream = false;
ByteArray m_compressedBuffer;
CompressionStream m_compressionStream;
DecompressionStream m_decompressionStream;
};
// Wraps a P2PSocket into a PacketSocket
class P2PPacketSocket : public PacketSocket {
class P2PPacketSocket : public CompressedPacketSocket {
public:
static P2PPacketSocketUPtr open(P2PSocketUPtr socket);

View File

@ -78,6 +78,11 @@ EnumMap<PacketType> const PacketTypeNames{
{PacketType::SystemObjectSpawn, "SystemObjectSpawn"}
};
EnumMap<NetCompressionMode> const NetCompressionModeNames {
{NetCompressionMode::None, "None"},
{NetCompressionMode::Zstd, "Zstd"}
};
Packet::~Packet() {}
void Packet::readLegacy(DataStream& ds) { read(ds); }
@ -187,15 +192,27 @@ void ProtocolRequestPacket::write(DataStream& ds) const {
ds.write(requestProtocolVersion);
}
ProtocolResponsePacket::ProtocolResponsePacket(bool allowed)
: allowed(allowed) {}
ProtocolResponsePacket::ProtocolResponsePacket(bool allowed, Json info)
: allowed(allowed), info(info) {}
void ProtocolResponsePacket::read(DataStream& ds) {
ds.read(allowed);
if (compressionMode() == PacketCompressionMode::Enabled) {
// gross hack for backwards compatibility with older OpenSB servers
// can be removed later
auto externalBuffer = as<DataStreamExternalBuffer>(&ds);
if (!externalBuffer || !externalBuffer->atEnd())
ds.read(info);
}
}
void ProtocolResponsePacket::writeLegacy(DataStream& ds) const {
ds.write(allowed);
}
void ProtocolResponsePacket::write(DataStream& ds) const {
ds.write(allowed);
writeLegacy(ds);
ds.write(info);
}
ConnectSuccessPacket::ConnectSuccessPacket() {}
@ -387,10 +404,10 @@ ClientConnectPacket::ClientConnectPacket() {}
ClientConnectPacket::ClientConnectPacket(ByteArray assetsDigest, bool allowAssetsMismatch, Uuid playerUuid,
String playerName, String playerSpecies, WorldChunks shipChunks, ShipUpgrades shipUpgrades,
bool introComplete, String account)
bool introComplete, String account, Json info)
: assetsDigest(std::move(assetsDigest)), allowAssetsMismatch(allowAssetsMismatch), playerUuid(std::move(playerUuid)),
playerName(std::move(playerName)), playerSpecies(std::move(playerSpecies)), shipChunks(std::move(shipChunks)),
shipUpgrades(std::move(shipUpgrades)), introComplete(std::move(introComplete)), account(std::move(account)) {}
shipUpgrades(std::move(shipUpgrades)), introComplete(std::move(introComplete)), account(std::move(account)), info(std::move(info)) {}
void ClientConnectPacket::readLegacy(DataStream& ds) {
ds.read(assetsDigest);
@ -402,6 +419,7 @@ void ClientConnectPacket::readLegacy(DataStream& ds) {
ds.read(shipUpgrades);
ds.read(introComplete);
ds.read(account);
info = Json();
}
void ClientConnectPacket::read(DataStream& ds) {

View File

@ -116,10 +116,16 @@ enum class PacketType : uint8_t {
};
extern EnumMap<PacketType> const PacketTypeNames;
enum class NetCompressionMode : uint8_t {
None,
Zstd
};
extern EnumMap<NetCompressionMode> const NetCompressionModeNames;
enum class PacketCompressionMode : uint8_t {
Disabled,
Enabled,
Automatic
Automatic,
Enabled
};
struct Packet {
@ -162,12 +168,14 @@ struct ProtocolRequestPacket : PacketBase<PacketType::ProtocolRequest> {
};
struct ProtocolResponsePacket : PacketBase<PacketType::ProtocolResponse> {
ProtocolResponsePacket(bool allowed = false);
ProtocolResponsePacket(bool allowed = false, Json info = {});
void read(DataStream& ds) override;
void writeLegacy(DataStream& ds) const override;
void write(DataStream& ds) const override;
bool allowed;
Json info;
};
struct ServerDisconnectPacket : PacketBase<PacketType::ServerDisconnect> {
@ -302,7 +310,7 @@ struct ClientConnectPacket : PacketBase<PacketType::ClientConnect> {
ClientConnectPacket();
ClientConnectPacket(ByteArray assetsDigest, bool allowAssetsMismatch, Uuid playerUuid, String playerName,
String playerSpecies, WorldChunks shipChunks, ShipUpgrades shipUpgrades, bool introComplete,
String account);
String account, Json info = {});
void readLegacy(DataStream& ds) override;
void read(DataStream& ds) override;

View File

@ -110,6 +110,8 @@ Object::Object(ObjectConfigConstPtr config, Json const& parameters) {
m_netGroup.setNeedsLoadCallback(bind(&Object::getNetStates, this, _1));
m_netGroup.setNeedsStoreCallback(bind(&Object::setNetStates, this));
m_clientEntityMode = ClientEntityModeNames.getLeft(configValue("clientEntityMode", "ClientSlaveOnly").toString());
}
Json Object::diskStore() const {
@ -127,6 +129,10 @@ EntityType Object::entityType() const {
return EntityType::Object;
}
ClientEntityMode Object::clientEntityMode() const {
return m_clientEntityMode;
}
void Object::init(World* world, EntityId entityId, EntityMode mode) {
Entity::init(world, entityId, mode);
// Only try and find a new orientation if we do not already have one,
@ -181,8 +187,12 @@ void Object::init(World* world, EntityId entityId, EntityMode mode) {
m_liquidCheckTimer.setDone();
setKeepAlive(configValue("keepAlive", false).toBool());
m_scriptComponent.setScripts(m_config->scripts);
auto jScripts = configValue("scripts", JsonArray());
if (jScripts.isType(Json::Type::Array))
m_scriptComponent.setScripts(jsonToStringList(jScripts).transformed(bind(AssetPath::relativeTo, m_config->path, _1)));
else
m_scriptComponent.setScripts(m_config->scripts);
m_scriptComponent.setUpdateDelta(configValue("scriptDelta", 5).toInt());
m_scriptComponent.addCallbacks("object", makeObjectCallbacks());

View File

@ -40,6 +40,7 @@ public:
ByteArray netStore();
virtual EntityType entityType() const override;
virtual ClientEntityMode clientEntityMode() const override;
virtual void init(World* world, EntityId entityId, EntityMode mode) override;
virtual void uninit() override;
@ -267,6 +268,8 @@ private:
NetElementHashMap<String, Json> m_scriptedAnimationParameters;
NetElementData<List<DamageSource>> m_damageSources;
ClientEntityMode m_clientEntityMode;
};
}

View File

@ -976,15 +976,21 @@ void Player::update(float dt, uint64_t) {
});
}
for (auto tool : {m_tools->primaryHandItem(), m_tools->altHandItem()}) {
for (auto& tool : {m_tools->primaryHandItem(), m_tools->altHandItem()}) {
if (auto inspectionTool = as<InspectionTool>(tool)) {
for (auto ir : inspectionTool->pullInspectionResults()) {
for (auto& ir : inspectionTool->pullInspectionResults()) {
if (ir.objectName) {
m_questManager->receiveMessage("objectScanned", true, {*ir.objectName, *ir.entityId});
m_log->addScannedObject(*ir.objectName);
}
addChatMessage(ir.message);
addChatMessage(ir.message, JsonObject{
{"message", JsonObject{
{"context", JsonObject{{"mode", "RadioMessage"}}},
{"fromConnection", world()->connection()},
{"text", ir.message}
}}
});
}
}
}
@ -2178,12 +2184,12 @@ void Player::queueItemPickupMessage(ItemPtr const& item) {
m_queuedItemPickups.append(item);
}
void Player::addChatMessage(String const& message) {
void Player::addChatMessage(String const& message, Json const& config) {
starAssert(!isSlave());
m_chatMessage = message;
m_chatMessageUpdated = true;
m_chatMessageChanged = true;
m_pendingChatActions.append(SayChatAction{entityId(), message, mouthPosition()});
m_pendingChatActions.append(SayChatAction{entityId(), message, mouthPosition(), config});
}
void Player::addEmote(HumanoidEmote const& emote, Maybe<float> emoteCooldown) {
@ -2196,6 +2202,10 @@ pair<HumanoidEmote, float> Player::currentEmote() const {
return make_pair(m_emoteState, m_emoteCooldownTimer);
}
Player::State Player::currentState() const {
return m_state;
}
List<ChatAction> Player::pullPendingChatActions() {
return take(m_pendingChatActions);
}

View File

@ -60,6 +60,21 @@ class Player :
public virtual EmoteEntity {
public:
enum class State {
Idle,
Walk,
Run,
Jump,
Fall,
Swim,
SwimIdle,
TeleportIn,
TeleportOut,
Crouch,
Lounge
};
static EnumMap<State> const StateNames;
Player(PlayerConfigPtr config, Uuid uuid = Uuid());
Player(PlayerConfigPtr config, ByteArray const& netStore);
Player(PlayerConfigPtr config, Json const& diskStore);
@ -381,10 +396,12 @@ public:
void queueUIMessage(String const& message) override;
void queueItemPickupMessage(ItemPtr const& item);
void addChatMessage(String const& message);
void addChatMessage(String const& message, Json const& config = {});
void addEmote(HumanoidEmote const& emote, Maybe<float> emoteCooldown = {});
pair<HumanoidEmote, float> currentEmote() const;
State currentState() const;
List<ChatAction> pullPendingChatActions() override;
Maybe<String> inspectionLogName() const override;
@ -483,21 +500,6 @@ public:
void setSecretProperty(String const& name, Json const& value);
private:
enum class State {
Idle,
Walk,
Run,
Jump,
Fall,
Swim,
SwimIdle,
TeleportIn,
TeleportOut,
Crouch,
Lounge
};
static EnumMap<State> const StateNames;
typedef LuaMessageHandlingComponent<LuaStorableComponent<LuaActorMovementComponent<LuaUpdatableComponent<LuaWorldComponent<LuaBaseComponent>>>>> GenericScriptComponent;
typedef shared_ptr<GenericScriptComponent> GenericScriptComponentPtr;

View File

@ -1008,12 +1008,15 @@ ItemPtr& PlayerInventory::retrieve(InventorySlot const& slot) {
if (auto es = slot.ptr<EquipmentSlot>())
return guardEmpty(m_equipment[*es]);
else if (auto bs = slot.ptr<BagSlot>())
return guardEmpty(m_bags[bs->first]->at(bs->second));
else if (slot.is<SwapSlot>())
else if (auto bs = slot.ptr<BagSlot>()) {
if (auto bag = m_bags.ptr(bs->first))
return guardEmpty((*bag)->at(bs->second));
} else if (slot.is<SwapSlot>())
return guardEmpty(m_swapSlot);
else
return guardEmpty(m_trashSlot);
throw ItemException::format("Invalid inventory slot {}", jsonFromInventorySlot(slot));
}
void PlayerInventory::swapCustomBarLinks(InventorySlot a, InventorySlot b) {

View File

@ -71,30 +71,30 @@ List<pair<Vec3I, OrbitBookmark>> PlayerUniverseMap::orbitBookmarks() const {
return bookmarks;
}
void PlayerUniverseMap::addOrbitBookmark(CelestialCoordinate const& system, OrbitBookmark const& bookmark) {
bool PlayerUniverseMap::addOrbitBookmark(CelestialCoordinate const& system, OrbitBookmark const& bookmark) {
if (system.isNull())
throw StarException("Cannot add orbit bookmark to null system");
m_universeMaps[*m_serverUuid].systems[system.location()].bookmarks.add(std::move(bookmark));
return m_universeMaps[*m_serverUuid].systems[system.location()].bookmarks.add(std::move(bookmark));
}
void PlayerUniverseMap::removeOrbitBookmark(CelestialCoordinate const& system, OrbitBookmark const& bookmark) {
bool PlayerUniverseMap::removeOrbitBookmark(CelestialCoordinate const& system, OrbitBookmark const& bookmark) {
if (system.isNull())
throw StarException("Cannot remove orbit bookmark from null system");
m_universeMaps[*m_serverUuid].systems[system.location()].bookmarks.remove(bookmark);
return m_universeMaps[*m_serverUuid].systems[system.location()].bookmarks.remove(bookmark);
}
List<TeleportBookmark> PlayerUniverseMap::teleportBookmarks() const {
return universeMap().teleportBookmarks.values();
}
void PlayerUniverseMap::addTeleportBookmark(TeleportBookmark bookmark) {
m_universeMaps[*m_serverUuid].teleportBookmarks.add(std::move(bookmark));
bool PlayerUniverseMap::addTeleportBookmark(TeleportBookmark bookmark) {
return m_universeMaps[*m_serverUuid].teleportBookmarks.add(std::move(bookmark));
}
void PlayerUniverseMap::removeTeleportBookmark(TeleportBookmark const& bookmark) {
m_universeMaps[*m_serverUuid].teleportBookmarks.remove(bookmark);
bool PlayerUniverseMap::removeTeleportBookmark(TeleportBookmark const& bookmark) {
return m_universeMaps[*m_serverUuid].teleportBookmarks.remove(bookmark);
}
void PlayerUniverseMap::invalidateWarpAction(WarpAction const& warpAction) {

Some files were not shown because too many files have changed in this diff Show More