Compare commits
134 Commits
version/20
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332d9dfadb | ||
|
|
c05802a498 | ||
|
|
0970bb1be2 | ||
|
|
c9d83fab6c | ||
|
|
edcce32f24 | ||
|
|
68d265f0e7 | ||
|
|
b985bb5757 | ||
|
|
f030816385 | ||
|
|
6f9f694eff | ||
|
|
a0d7f0e172 | ||
|
|
9f98e438cb | ||
|
|
f029ca7b64 | ||
|
|
ca6c6dd6d3 | ||
|
|
4ba4ea5602 | ||
|
|
ca5f66da9f | ||
|
|
92d053d850 | ||
|
|
001ae80723 | ||
|
|
58d9a6d0b5 | ||
|
|
261316cb45 | ||
|
|
2ad4d2e672 | ||
|
|
1d9fa929fa | ||
|
|
26bb6236a0 | ||
|
|
b2acf5a623 | ||
|
|
bea88bb3a9 | ||
|
|
def2af2bdd | ||
|
|
6f6d705f22 | ||
|
|
e9c33104d3 | ||
|
|
985897f8ba | ||
|
|
4251b28e88 | ||
|
|
76540a211c | ||
|
|
904fc5a7dd | ||
|
|
4aebc159d5 | ||
|
|
f89227fc1a | ||
|
|
f50f383cc0 | ||
|
|
706ab387de | ||
|
|
cddfdb7d1d | ||
|
|
3c64578848 | ||
|
|
e1fe9b45e0 | ||
|
|
8fdc1d306f | ||
|
|
6e0c39bb68 | ||
|
|
f20b416cfe | ||
|
|
47e06b5216 | ||
|
|
bfcdf22705 | ||
|
|
d0db407faa | ||
|
|
d95b1c0441 | ||
|
|
837ba86d57 | ||
|
|
0cb1b463e1 | ||
|
|
8a772c8edd | ||
|
|
03bdad0a10 | ||
|
|
537776e1f8 | ||
|
|
f34a4a304e | ||
|
|
a59c4e2c8b | ||
|
|
46f4967f6e | ||
|
|
9305417207 | ||
|
|
11da8b33f9 | ||
|
|
c7b320eb55 | ||
|
|
72b2eb0ebf | ||
|
|
ec3829addb | ||
|
|
96bafef3f3 | ||
|
|
3ff3bd0a6c | ||
|
|
78d073a0f0 | ||
|
|
d62796c19d | ||
|
|
7ea7ff43fc | ||
|
|
dafd185595 | ||
|
|
1568ed8b97 | ||
|
|
444e2ffb2d | ||
|
|
32ccdaec6f | ||
|
|
bd9f04d980 | ||
|
|
9c530d6978 | ||
|
|
05094510be | ||
|
|
41e43eeba0 | ||
|
|
f95cbd703a | ||
|
|
e39036a635 | ||
|
|
1171d57b72 | ||
|
|
852058150b | ||
|
|
9be955262e | ||
|
|
bfa411e9b7 | ||
|
|
dab015742a | ||
|
|
0ab81d36b9 | ||
|
|
4d905135e8 | ||
|
|
afad224ca0 | ||
|
|
99c159d46e | ||
|
|
733efd08dd | ||
|
|
3753c62783 | ||
|
|
3e804605b7 | ||
|
|
35b1d321fe | ||
|
|
6ab7f68f4b | ||
|
|
b2e149a737 | ||
|
|
e28c4fa5ca | ||
|
|
61dc19f635 | ||
|
|
60634bc445 | ||
|
|
b3e93eaf6e | ||
|
|
36dca92c2b | ||
|
|
5a1ed52d7c | ||
|
|
fd191b51ce | ||
|
|
6167159795 | ||
|
|
672afdbc34 | ||
|
|
27e61b3dec | ||
|
|
f1d00c9b40 | ||
|
|
1b00ece8c4 | ||
|
|
d0f24229b2 | ||
|
|
ab8795f6dc | ||
|
|
2fe60c9635 | ||
|
|
27a3ee3bce | ||
|
|
0721db3acd | ||
|
|
24b58cbe21 | ||
|
|
5b3274e688 | ||
|
|
fcd75cfae5 | ||
|
|
6387a1d6d0 | ||
|
|
fc4ce2528b | ||
|
|
18d2bfcd8b | ||
|
|
6738a3aa2b | ||
|
|
a8c1bef0bf | ||
|
|
4faf0ea468 | ||
|
|
80cc09fe90 | ||
|
|
ca8dbb985e | ||
|
|
751cdc32a8 | ||
|
|
1d978429f5 | ||
|
|
2b2e3ae5c4 | ||
|
|
0e3ac7e078 | ||
|
|
01ab3b2385 | ||
|
|
11cf87951c | ||
|
|
7c004a4c90 | ||
|
|
a48693d273 | ||
|
|
12d57a6373 | ||
|
|
c7c8fc7777 | ||
|
|
6e054e57ef | ||
|
|
1d89a76d13 | ||
|
|
65925cccdf | ||
|
|
898559ab31 | ||
|
|
ad3621e23b | ||
|
|
57b0f70a55 | ||
|
|
8d6e543cc2 | ||
|
|
bd93fb279b |
@@ -10,6 +10,12 @@ if(COMMAND cmake_policy)
|
||||
if(POLICY CMP0067)
|
||||
cmake_policy(SET CMP0067 NEW)
|
||||
endif()
|
||||
|
||||
# OpenGL VND policy : use the old definition for now, until we can audit this
|
||||
if(POLICY CMP0072)
|
||||
cmake_policy(SET CMP0072 OLD)
|
||||
endif()
|
||||
|
||||
if(POLICY CMP0093)
|
||||
cmake_policy(SET CMP0093 NEW)
|
||||
endif()
|
||||
@@ -43,7 +49,7 @@ set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
|
||||
# read 'version' file into a variable (stripping any newlines or spaces)
|
||||
file(READ version versionFile)
|
||||
file(READ simgear-version versionFile)
|
||||
string(STRIP ${versionFile} SIMGEAR_VERSION)
|
||||
|
||||
project(SimGear VERSION ${SIMGEAR_VERSION} LANGUAGES C CXX)
|
||||
@@ -272,7 +278,15 @@ else()
|
||||
endif()
|
||||
endif(SIMGEAR_HEADLESS)
|
||||
|
||||
find_package(ZLIB 1.2.4 REQUIRED)
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||
# As of 2020-08-01, OpenBSD's system zlib is slightly old, but it's usable
|
||||
# with a workaround in simgear/io/iostreams/gzfstream.cxx.
|
||||
find_package(ZLIB 1.2.3 REQUIRED)
|
||||
else()
|
||||
find_package(ZLIB 1.2.4 REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(LibLZMA REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
if (SYSTEM_EXPAT)
|
||||
@@ -417,16 +431,8 @@ if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
message(WARNING "GCC 4.4 will be required soon, please upgrade")
|
||||
endif()
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_C_FLAGS
|
||||
"${CMAKE_C_FLAGS} -O0 -fno-omit-frame-pointer -fno-inline")
|
||||
set(CMAKE_CXX_FLAGS
|
||||
"${CMAKE_CXX_FLAGS} -O0 -fno-omit-frame-pointer -fno-inline")
|
||||
elseif (ENABLE_SIMD)
|
||||
if (X86 OR X86_64)
|
||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
|
||||
endif()
|
||||
if (X86 OR X86_64)
|
||||
set(SIMD_COMPILER_FLAGS "-msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
|
||||
endif()
|
||||
|
||||
# certain GCC versions don't provide the atomic builds, and hence
|
||||
@@ -435,6 +441,10 @@ if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
check_cxx_source_compiles(
|
||||
"int main() { unsigned mValue; return __sync_add_and_fetch(&mValue, 1); }"
|
||||
GCC_ATOMIC_BUILTINS_FOUND)
|
||||
|
||||
# override CMake default RelWithDebInfo flags.
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
|
||||
endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
if (CLANG)
|
||||
@@ -449,16 +459,7 @@ if (CLANG)
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O3 -g -DNDEBUG")
|
||||
|
||||
if (ENABLE_SIMD)
|
||||
if (X86 OR X86_64)
|
||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
|
||||
|
||||
# propogate to the RelWithDebInfo flags
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g -DNDEBUG")
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -g -DNDEBUG")
|
||||
endif()
|
||||
endif()
|
||||
set(SIMD_COMPILER_FLAGS "-msse2 -mfpmath=sse -ftree-vectorize -ftree-slp-vectorize")
|
||||
endif()
|
||||
|
||||
if (ENABLE_OPENMP)
|
||||
@@ -490,14 +491,8 @@ if(WIN32)
|
||||
|
||||
if(MSVC)
|
||||
set(MSVC_FLAGS "-DWIN32 -DNOMINMAX -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS -D__CRT_NONSTDC_NO_WARNINGS /MP")
|
||||
if(ENABLE_SIMD)
|
||||
if (X86)
|
||||
SET(CMAKE_C_FLAGS_RELEASE "/O2 /arch:SSE /arch:SSE2")
|
||||
SET(CMAKE_CXX_FLAGS_RELEASE "/O2 /arch:SSE /arch:SSE2")
|
||||
else()
|
||||
SET(CMAKE_C_FLAGS_RELEASE "/O2")
|
||||
SET(CMAKE_CXX_FLAGS_RELEASE "/O2")
|
||||
endif()
|
||||
if (X86)
|
||||
set(SIMD_COMPILER_FLAGS "/arch:SSE /arch:SSE2")
|
||||
endif()
|
||||
|
||||
if (NOT OSG_FSTREAM_EXPORT_FIXED)
|
||||
@@ -521,6 +516,20 @@ if(WIN32)
|
||||
set( RT_LIBRARY "winmm" )
|
||||
endif(WIN32)
|
||||
|
||||
# append the SIMD flags if requested
|
||||
if (ENABLE_SIMD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SIMD_COMPILER_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SIMD_COMPILER_FLAGS}")
|
||||
|
||||
# set for multi-configuration generators
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${SIMD_COMPILER_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${SIMD_COMPILER_FLAGS}")
|
||||
|
||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${SIMD_COMPILER_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${SIMD_COMPILER_FLAGS}")
|
||||
endif()
|
||||
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS_C} ${MSVC_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_CXX} ${MSVC_FLAGS} ${BOOST_CXX_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MSVC_LD_FLAGS}")
|
||||
|
||||
124
CMakeModules/FindLibLZMA.cmake
Normal file
124
CMakeModules/FindLibLZMA.cmake
Normal file
@@ -0,0 +1,124 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindLibLZMA
|
||||
-----------
|
||||
|
||||
Find LZMA compression algorithm headers and library.
|
||||
|
||||
|
||||
Imported Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module defines :prop_tgt:`IMPORTED` target ``LibLZMA::LibLZMA``, if
|
||||
liblzma has been found.
|
||||
|
||||
Result variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module will set the following variables in your project:
|
||||
|
||||
``LIBLZMA_FOUND``
|
||||
True if liblzma headers and library were found.
|
||||
``LIBLZMA_INCLUDE_DIRS``
|
||||
Directory where liblzma headers are located.
|
||||
``LIBLZMA_LIBRARIES``
|
||||
Lzma libraries to link against.
|
||||
``LIBLZMA_HAS_AUTO_DECODER``
|
||||
True if lzma_auto_decoder() is found (required).
|
||||
``LIBLZMA_HAS_EASY_ENCODER``
|
||||
True if lzma_easy_encoder() is found (required).
|
||||
``LIBLZMA_HAS_LZMA_PRESET``
|
||||
True if lzma_lzma_preset() is found (required).
|
||||
``LIBLZMA_VERSION_MAJOR``
|
||||
The major version of lzma
|
||||
``LIBLZMA_VERSION_MINOR``
|
||||
The minor version of lzma
|
||||
``LIBLZMA_VERSION_PATCH``
|
||||
The patch version of lzma
|
||||
``LIBLZMA_VERSION_STRING``
|
||||
version number as a string (ex: "5.0.3")
|
||||
#]=======================================================================]
|
||||
|
||||
find_path(LIBLZMA_INCLUDE_DIR lzma.h )
|
||||
if(NOT LIBLZMA_LIBRARY)
|
||||
find_library(LIBLZMA_LIBRARY_RELEASE NAMES lzma liblzma NAMES_PER_DIR PATH_SUFFIXES lib)
|
||||
find_library(LIBLZMA_LIBRARY_DEBUG NAMES lzmad liblzmad NAMES_PER_DIR PATH_SUFFIXES lib)
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(LIBLZMA)
|
||||
else()
|
||||
file(TO_CMAKE_PATH "${LIBLZMA_LIBRARY}" LIBLZMA_LIBRARY)
|
||||
endif()
|
||||
|
||||
if(LIBLZMA_INCLUDE_DIR AND EXISTS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h")
|
||||
file(STRINGS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h" LIBLZMA_HEADER_CONTENTS REGEX "#define LZMA_VERSION_[A-Z]+ [0-9]+")
|
||||
|
||||
string(REGEX REPLACE ".*#define LZMA_VERSION_MAJOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MAJOR "${LIBLZMA_HEADER_CONTENTS}")
|
||||
string(REGEX REPLACE ".*#define LZMA_VERSION_MINOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MINOR "${LIBLZMA_HEADER_CONTENTS}")
|
||||
string(REGEX REPLACE ".*#define LZMA_VERSION_PATCH ([0-9]+).*" "\\1" LIBLZMA_VERSION_PATCH "${LIBLZMA_HEADER_CONTENTS}")
|
||||
|
||||
set(LIBLZMA_VERSION_STRING "${LIBLZMA_VERSION_MAJOR}.${LIBLZMA_VERSION_MINOR}.${LIBLZMA_VERSION_PATCH}")
|
||||
unset(LIBLZMA_HEADER_CONTENTS)
|
||||
endif()
|
||||
|
||||
# We're using new code known now as XZ, even library still been called LZMA
|
||||
# it can be found in http://tukaani.org/xz/
|
||||
# Avoid using old codebase
|
||||
if (LIBLZMA_LIBRARY)
|
||||
include(CheckLibraryExists)
|
||||
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
|
||||
set(CMAKE_REQUIRED_QUIET ${LibLZMA_FIND_QUIETLY})
|
||||
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
|
||||
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY})
|
||||
elseif(LIBLZMA_LIBRARY_RELEASE)
|
||||
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_RELEASE})
|
||||
elseif(LIBLZMA_LIBRARY_DEBUG)
|
||||
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_DEBUG})
|
||||
endif()
|
||||
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_auto_decoder "" LIBLZMA_HAS_AUTO_DECODER)
|
||||
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_easy_encoder "" LIBLZMA_HAS_EASY_ENCODER)
|
||||
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_lzma_preset "" LIBLZMA_HAS_LZMA_PRESET)
|
||||
unset(LIBLZMA_LIBRARY_check)
|
||||
set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibLZMA REQUIRED_VARS LIBLZMA_LIBRARY
|
||||
LIBLZMA_INCLUDE_DIR
|
||||
LIBLZMA_HAS_AUTO_DECODER
|
||||
LIBLZMA_HAS_EASY_ENCODER
|
||||
LIBLZMA_HAS_LZMA_PRESET
|
||||
VERSION_VAR LIBLZMA_VERSION_STRING
|
||||
)
|
||||
mark_as_advanced( LIBLZMA_INCLUDE_DIR LIBLZMA_LIBRARY )
|
||||
|
||||
if (LIBLZMA_FOUND)
|
||||
set(LIBLZMA_LIBRARIES ${LIBLZMA_LIBRARY})
|
||||
set(LIBLZMA_INCLUDE_DIRS ${LIBLZMA_INCLUDE_DIR})
|
||||
if(NOT TARGET LibLZMA::LibLZMA)
|
||||
add_library(LibLZMA::LibLZMA UNKNOWN IMPORTED)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${LIBLZMA_INCLUDE_DIR}
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C)
|
||||
|
||||
if(LIBLZMA_LIBRARY_RELEASE)
|
||||
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${LIBLZMA_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
|
||||
if(LIBLZMA_LIBRARY_DEBUG)
|
||||
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${LIBLZMA_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
|
||||
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
|
||||
set_target_properties(LibLZMA::LibLZMA PROPERTIES
|
||||
IMPORTED_LOCATION "${LIBLZMA_LIBRARY}")
|
||||
endif()
|
||||
endif()
|
||||
endif ()
|
||||
@@ -1,6 +1,7 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
find_dependency(ZLIB)
|
||||
find_dependency(LibLZMA)
|
||||
find_dependency(Threads)
|
||||
|
||||
# OSG
|
||||
|
||||
1
simgear-version
Normal file
1
simgear-version
Normal file
@@ -0,0 +1 @@
|
||||
2020.3.8
|
||||
@@ -167,7 +167,10 @@ target_link_libraries(SimGearCore PRIVATE
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${COCOA_LIBRARY}
|
||||
${CURL_LIBRARIES}
|
||||
${WINSOCK_LIBRARY})
|
||||
${WINSOCK_LIBRARY}
|
||||
${SHLWAPI_LIBRARY}
|
||||
LibLZMA::LibLZMA
|
||||
)
|
||||
|
||||
if(SYSTEM_EXPAT)
|
||||
target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES})
|
||||
|
||||
@@ -691,6 +691,10 @@ namespace canvas
|
||||
{
|
||||
_sampling_dirty = true;
|
||||
}
|
||||
else if( name == "anisotropy" )
|
||||
{
|
||||
_texture.setMaxAnisotropy( node->getFloatValue() );
|
||||
}
|
||||
else if( name == "additive-blend" )
|
||||
{
|
||||
_texture.useAdditiveBlend( node->getBoolValue() );
|
||||
|
||||
@@ -202,6 +202,12 @@ namespace canvas
|
||||
updateSampling();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setMaxAnisotropy(float anis)
|
||||
{
|
||||
texture->setMaxAnisotropy(anis);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void ODGauge::setRender(bool render)
|
||||
{
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace canvas
|
||||
int coverage_samples = 0,
|
||||
int color_samples = 0 );
|
||||
|
||||
void setMaxAnisotropy(float anis);
|
||||
|
||||
/**
|
||||
* Enable/Disable updating the texture (If disabled the contents of the
|
||||
* texture remains with the outcome of the last rendering pass)
|
||||
|
||||
@@ -850,7 +850,7 @@ namespace canvas
|
||||
void fillRow(GLubyte* row, GLuint pixel, GLuint width, GLuint pixelBytes)
|
||||
{
|
||||
GLubyte* dst = row;
|
||||
for (int x = 0; x < width; ++x) {
|
||||
for (GLuint x = 0; x < width; ++x) {
|
||||
memcpy(dst, &pixel, pixelBytes);
|
||||
dst += pixelBytes;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace canvas
|
||||
int stretch,
|
||||
uint8_t alignment )
|
||||
{
|
||||
ItemData item_data = {0};
|
||||
ItemData item_data = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
item_data.layout_item = item;
|
||||
item_data.stretch = std::max(0, stretch);
|
||||
|
||||
|
||||
@@ -218,6 +218,9 @@ const float SG_RADIANS_TO_DEGREES = 180.0f / SG_PI;
|
||||
#define SG_OBJECT_RANGE_ROUGH 9000.0
|
||||
#define SG_OBJECT_RANGE_DETAILED 1500.0
|
||||
|
||||
/** Minimum expiry time of PagedLOD within the Tile. Overridden by /sim/rendering/plod-minimum-expiry-time-secs **/
|
||||
#define SG_TILE_MIN_EXPIRY 180.0
|
||||
|
||||
/** Radius of scenery tiles in m **/
|
||||
#define SG_TILE_RADIUS 14000.0
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <vector>
|
||||
#include <memory> // for std::unique_ptr
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/debug/LogCallback.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
|
||||
include (SimGearComponent)
|
||||
|
||||
set(HEADERS debug_types.h logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx)
|
||||
set(SOURCES logstream.cxx BufferedLogCallback.cxx)
|
||||
set(HEADERS debug_types.h
|
||||
logstream.hxx BufferedLogCallback.hxx OsgIoCapture.hxx
|
||||
LogCallback.hxx LogEntry.hxx
|
||||
ErrorReportingCallback.hxx)
|
||||
|
||||
set(SOURCES logstream.cxx BufferedLogCallback.cxx
|
||||
LogCallback.cxx LogEntry.cxx
|
||||
ErrorReportingCallback.cxx
|
||||
)
|
||||
|
||||
simgear_component(debug debug "${SOURCES}" "${HEADERS}")
|
||||
|
||||
47
simgear/debug/ErrorReportingCallback.cxx
Normal file
47
simgear/debug/ErrorReportingCallback.cxx
Normal file
@@ -0,0 +1,47 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "ErrorReportingCallback.hxx"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace simgear {
|
||||
|
||||
static ErrorReportCallback static_callback;
|
||||
|
||||
void setErrorReportCallback(ErrorReportCallback cb)
|
||||
{
|
||||
static_callback = cb;
|
||||
}
|
||||
|
||||
|
||||
void reportError(const std::string& msg, const std::string& more)
|
||||
{
|
||||
if (!static_callback)
|
||||
return;
|
||||
|
||||
static_callback(msg, more, false);
|
||||
}
|
||||
|
||||
void reportFatalError(const std::string& msg, const std::string& more)
|
||||
{
|
||||
if (!static_callback)
|
||||
return;
|
||||
static_callback(msg, more, true);
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
31
simgear/debug/ErrorReportingCallback.hxx
Normal file
31
simgear/debug/ErrorReportingCallback.hxx
Normal file
@@ -0,0 +1,31 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace simgear {
|
||||
|
||||
void reportError(const std::string& msg, const std::string& more = {});
|
||||
|
||||
void reportFatalError(const std::string& msg, const std::string& more = {});
|
||||
|
||||
using ErrorReportCallback = std::function<void(const std::string& msg, const std::string& more, bool isFatal)>;
|
||||
|
||||
void setErrorReportCallback(ErrorReportCallback cb);
|
||||
|
||||
} // namespace simgear
|
||||
116
simgear/debug/LogCallback.cxx
Normal file
116
simgear/debug/LogCallback.cxx
Normal file
@@ -0,0 +1,116 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "LogCallback.hxx"
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) : m_class(c),
|
||||
m_priority(p)
|
||||
{
|
||||
}
|
||||
|
||||
void LogCallback::operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
{
|
||||
// override me
|
||||
}
|
||||
|
||||
bool LogCallback::doProcessEntry(const LogEntry& e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::processEntry(const LogEntry& e)
|
||||
{
|
||||
if (doProcessEntry(e))
|
||||
return; // derived class used the new API
|
||||
|
||||
// call the old API
|
||||
(*this)(e.debugClass, e.debugPriority, e.file, e.line, e.message);
|
||||
}
|
||||
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
if ((c & m_class) != 0 && p >= m_priority)
|
||||
return true;
|
||||
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels(sgDebugClass c, sgDebugPriority p)
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
|
||||
{
|
||||
switch (p) {
|
||||
case SG_DEV_ALERT:
|
||||
case SG_ALERT:
|
||||
return "ALRT";
|
||||
case SG_BULK: return "BULK";
|
||||
case SG_DEBUG: return "DBUG";
|
||||
case SG_MANDATORY_INFO:
|
||||
case SG_INFO:
|
||||
return "INFO";
|
||||
case SG_POPUP: return "POPU";
|
||||
case SG_DEV_WARN:
|
||||
case SG_WARN:
|
||||
return "WARN";
|
||||
|
||||
default: return "UNKN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* LogCallback::debugClassToString(sgDebugClass c)
|
||||
{
|
||||
switch (c) {
|
||||
case SG_NONE: return "none";
|
||||
case SG_TERRAIN: return "terrain";
|
||||
case SG_ASTRO: return "astro";
|
||||
case SG_FLIGHT: return "flight";
|
||||
case SG_INPUT: return "input";
|
||||
case SG_GL: return "opengl";
|
||||
case SG_VIEW: return "view";
|
||||
case SG_COCKPIT: return "cockpit";
|
||||
case SG_GENERAL: return "general";
|
||||
case SG_MATH: return "math";
|
||||
case SG_EVENT: return "event";
|
||||
case SG_AIRCRAFT: return "aircraft";
|
||||
case SG_AUTOPILOT: return "autopilot";
|
||||
case SG_IO: return "io";
|
||||
case SG_CLIPPER: return "clipper";
|
||||
case SG_NETWORK: return "network";
|
||||
case SG_ATC: return "atc";
|
||||
case SG_NASAL: return "nasal";
|
||||
case SG_INSTR: return "instruments";
|
||||
case SG_SYSTEMS: return "systems";
|
||||
case SG_AI: return "ai";
|
||||
case SG_ENVIRONMENT: return "environment";
|
||||
case SG_SOUND: return "sound";
|
||||
case SG_NAVAID: return "navaid";
|
||||
case SG_GUI: return "gui";
|
||||
case SG_TERRASYNC: return "terrasync";
|
||||
case SG_PARTICLES: return "particles";
|
||||
case SG_HEADLESS: return "headless";
|
||||
case SG_OSG: return "OSG";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
56
simgear/debug/LogCallback.hxx
Normal file
56
simgear/debug/LogCallback.hxx
Normal file
@@ -0,0 +1,56 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "LogEntry.hxx"
|
||||
#include "debug_types.h"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class LogCallback
|
||||
{
|
||||
public:
|
||||
virtual ~LogCallback() = default;
|
||||
|
||||
// newer API: return true if you handled the message, otherwise
|
||||
// the old API will be called
|
||||
virtual bool doProcessEntry(const LogEntry& e);
|
||||
|
||||
// old API, kept for compatability
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage);
|
||||
|
||||
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
void processEntry(const LogEntry& e);
|
||||
|
||||
protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
|
||||
static const char* debugClassToString(sgDebugClass c);
|
||||
static const char* debugPriorityToString(sgDebugPriority p);
|
||||
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
|
||||
} // namespace simgear
|
||||
45
simgear/debug/LogEntry.cxx
Normal file
45
simgear/debug/LogEntry.cxx
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "LogEntry.hxx"
|
||||
|
||||
#include <cstring> // for strdup
|
||||
|
||||
namespace simgear {
|
||||
|
||||
LogEntry::~LogEntry()
|
||||
{
|
||||
if (freeFilename) {
|
||||
free(const_cast<char*>(file));
|
||||
}
|
||||
}
|
||||
|
||||
LogEntry::LogEntry(const LogEntry& c) : debugClass(c.debugClass),
|
||||
debugPriority(c.debugPriority),
|
||||
originalPriority(c.originalPriority),
|
||||
file(c.file),
|
||||
line(c.line),
|
||||
message(c.message),
|
||||
freeFilename(c.freeFilename)
|
||||
{
|
||||
if (c.freeFilename) {
|
||||
file = strdup(c.file);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
54
simgear/debug/LogEntry.hxx
Normal file
54
simgear/debug/LogEntry.hxx
Normal file
@@ -0,0 +1,54 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "debug_types.h"
|
||||
|
||||
namespace simgear {
|
||||
/**
|
||||
* storage of a single log entry. This is used to pass log entries from
|
||||
* the various threads to the logging thread, and also to store the startup
|
||||
* entries
|
||||
*/
|
||||
class LogEntry
|
||||
{
|
||||
public:
|
||||
LogEntry(sgDebugClass c, sgDebugPriority p,
|
||||
sgDebugPriority op,
|
||||
const char* f, int l, const std::string& msg) : debugClass(c), debugPriority(p), originalPriority(op),
|
||||
file(f), line(l),
|
||||
message(msg)
|
||||
{
|
||||
}
|
||||
|
||||
LogEntry(const LogEntry& c);
|
||||
LogEntry& operator=(const LogEntry& c) = delete;
|
||||
|
||||
~LogEntry();
|
||||
|
||||
const sgDebugClass debugClass;
|
||||
const sgDebugPriority debugPriority;
|
||||
const sgDebugPriority originalPriority;
|
||||
const char* file;
|
||||
const int line;
|
||||
const std::string message;
|
||||
|
||||
bool freeFilename = false; ///< if true, we own, and therefore need to free, the memory pointed to by 'file'
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
#include <osg/Notify>
|
||||
|
||||
using namespace osg;
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
/**
|
||||
* merge OSG output into our logging system, so it gets recorded to file,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
/** \file debug_types.h
|
||||
* Define the various logging classes and priorities
|
||||
*/
|
||||
@@ -52,16 +54,17 @@ typedef enum {
|
||||
* appended, or the priority Nasal reports to compiled code will change.
|
||||
*/
|
||||
typedef enum {
|
||||
SG_BULK = 1, // For frequent messages
|
||||
SG_DEBUG, // Less frequent debug type messages
|
||||
SG_INFO, // Informatory messages
|
||||
SG_WARN, // Possible impending problem
|
||||
SG_ALERT, // Very possible impending problem
|
||||
SG_POPUP, // Severe enough to alert using a pop-up window
|
||||
SG_BULK = 1, // For frequent messages
|
||||
SG_DEBUG, // Less frequent debug type messages
|
||||
SG_INFO, // Informatory messages
|
||||
SG_WARN, // Possible impending problem
|
||||
SG_ALERT, // Very possible impending problem
|
||||
SG_POPUP, // Severe enough to alert using a pop-up window
|
||||
// SG_EXIT, // Problem (no core)
|
||||
// SG_ABORT // Abandon ship (core)
|
||||
|
||||
SG_DEV_WARN, // Warning for developers, translated to other priority
|
||||
SG_DEV_ALERT // Alert for developers, translated
|
||||
} sgDebugPriority;
|
||||
SG_DEV_WARN, // Warning for developers, translated to other priority
|
||||
SG_DEV_ALERT, // Alert for developers, translated
|
||||
|
||||
SG_MANDATORY_INFO // information, but should always be shown
|
||||
} sgDebugPriority;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <simgear/threads/SGThread.hxx>
|
||||
#include <simgear/threads/SGQueue.hxx>
|
||||
|
||||
#include "LogCallback.hxx"
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
@@ -47,86 +48,9 @@
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
LogCallback::LogCallback(sgDebugClass c, sgDebugPriority p) :
|
||||
m_class(c),
|
||||
m_priority(p)
|
||||
{
|
||||
}
|
||||
|
||||
bool LogCallback::shouldLog(sgDebugClass c, sgDebugPriority p) const
|
||||
{
|
||||
|
||||
if ((c & m_class) != 0 && p >= m_priority)
|
||||
return true;
|
||||
if (c == SG_OSG) // always have OSG logging as it OSG logging is configured separately.
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogCallback::setLogLevels( sgDebugClass c, sgDebugPriority p )
|
||||
{
|
||||
m_priority = p;
|
||||
m_class = c;
|
||||
}
|
||||
const char* LogCallback::debugPriorityToString(sgDebugPriority p)
|
||||
{
|
||||
switch (p) {
|
||||
case SG_ALERT: return "ALRT";
|
||||
case SG_BULK: return "BULK";
|
||||
case SG_DEBUG: return "DBUG";
|
||||
case SG_INFO: return "INFO";
|
||||
case SG_POPUP: return "POPU";
|
||||
case SG_WARN: return "WARN";
|
||||
default: return "UNKN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* LogCallback::debugClassToString(sgDebugClass c)
|
||||
{
|
||||
switch (c) {
|
||||
case SG_NONE: return "none";
|
||||
case SG_TERRAIN: return "terrain";
|
||||
case SG_ASTRO: return "astro";
|
||||
case SG_FLIGHT: return "flight";
|
||||
case SG_INPUT: return "input";
|
||||
case SG_GL: return "opengl";
|
||||
case SG_VIEW: return "view";
|
||||
case SG_COCKPIT: return "cockpit";
|
||||
case SG_GENERAL: return "general";
|
||||
case SG_MATH: return "math";
|
||||
case SG_EVENT: return "event";
|
||||
case SG_AIRCRAFT: return "aircraft";
|
||||
case SG_AUTOPILOT: return "autopilot";
|
||||
case SG_IO: return "io";
|
||||
case SG_CLIPPER: return "clipper";
|
||||
case SG_NETWORK: return "network";
|
||||
case SG_ATC: return "atc";
|
||||
case SG_NASAL: return "nasal";
|
||||
case SG_INSTR: return "instruments";
|
||||
case SG_SYSTEMS: return "systems";
|
||||
case SG_AI: return "ai";
|
||||
case SG_ENVIRONMENT:return "environment";
|
||||
case SG_SOUND: return "sound";
|
||||
case SG_NAVAID: return "navaid";
|
||||
case SG_GUI: return "gui";
|
||||
case SG_TERRASYNC: return "terrasync";
|
||||
case SG_PARTICLES: return "particles";
|
||||
case SG_HEADLESS: return "headless";
|
||||
case SG_OSG: return "OSG";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class FileLogCallback : public simgear::LogCallback
|
||||
{
|
||||
@@ -139,8 +63,8 @@ public:
|
||||
logTimer.stamp();
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message)
|
||||
void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& message) override
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
@@ -196,8 +120,8 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) override
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
//fprintf(stderr, "%s\n", aMessage.c_str());
|
||||
@@ -226,8 +150,8 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage)
|
||||
void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) override
|
||||
{
|
||||
if (!shouldLog(c, p)) return;
|
||||
|
||||
@@ -242,28 +166,6 @@ public:
|
||||
class logstream::LogStreamPrivate : public SGThread
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* storage of a single log entry. This is used to pass log entries from
|
||||
* the various threads to the logging thread, and also to store the startup
|
||||
* entries
|
||||
*/
|
||||
class LogEntry
|
||||
{
|
||||
public:
|
||||
LogEntry(sgDebugClass c, sgDebugPriority p,
|
||||
const char* f, int l, const std::string& msg) :
|
||||
debugClass(c), debugPriority(p), file(f), line(l),
|
||||
message(msg)
|
||||
{
|
||||
}
|
||||
|
||||
const sgDebugClass debugClass;
|
||||
const sgDebugPriority debugPriority;
|
||||
const char* file;
|
||||
const int line;
|
||||
const std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* RAII object to pause the logging thread if it's running, and restart it.
|
||||
* used to safely make configuration changes.
|
||||
@@ -401,13 +303,20 @@ public:
|
||||
~LogStreamPrivate()
|
||||
{
|
||||
removeCallbacks();
|
||||
|
||||
// house-keeping, avoid leak warnings if we exit before disabling
|
||||
// startup logging
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_lock);
|
||||
clearStartupEntriesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_lock;
|
||||
SGBlockingQueue<LogEntry> m_entries;
|
||||
SGBlockingQueue<simgear::LogEntry> m_entries;
|
||||
|
||||
// log entries posted during startup
|
||||
std::vector<LogEntry> m_startupEntries;
|
||||
std::vector<simgear::LogEntry> m_startupEntries;
|
||||
bool m_startupLogging = false;
|
||||
|
||||
typedef std::vector<simgear::LogCallback*> CallbackVec;
|
||||
@@ -430,6 +339,8 @@ public:
|
||||
// test suite mode.
|
||||
bool m_testMode = false;
|
||||
|
||||
std::vector<std::string> _popupMessages;
|
||||
|
||||
void startLog()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_lock);
|
||||
@@ -447,14 +358,19 @@ public:
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_lock);
|
||||
m_startupLogging = on;
|
||||
m_startupEntries.clear();
|
||||
clearStartupEntriesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void run()
|
||||
void clearStartupEntriesLocked()
|
||||
{
|
||||
m_startupEntries.clear();
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
while (1) {
|
||||
LogEntry entry(m_entries.pop());
|
||||
simgear::LogEntry entry(m_entries.pop());
|
||||
// special marker entry detected, terminate the thread since we are
|
||||
// making a configuration change or quitting the app
|
||||
if ((entry.debugClass == SG_NONE) && entry.file && !strcmp(entry.file, "done")) {
|
||||
@@ -470,8 +386,7 @@ public:
|
||||
}
|
||||
// submit to each installed callback in turn
|
||||
for (simgear::LogCallback* cb : m_callbacks) {
|
||||
(*cb)(entry.debugClass, entry.debugPriority,
|
||||
entry.file, entry.line, entry.message);
|
||||
cb->processEntry(entry);
|
||||
}
|
||||
} // of main thread loop
|
||||
}
|
||||
@@ -486,7 +401,7 @@ public:
|
||||
|
||||
// log a special marker value, which will cause the thread to wakeup,
|
||||
// and then exit
|
||||
log(SG_NONE, SG_ALERT, "done", -1, "");
|
||||
log(SG_NONE, SG_ALERT, "done", -1, "", false);
|
||||
}
|
||||
join();
|
||||
|
||||
@@ -501,9 +416,8 @@ public:
|
||||
|
||||
// we clear startup entries not using this, so always safe to run
|
||||
// this code, container will simply be empty
|
||||
for (auto entry : m_startupEntries) {
|
||||
(*cb)(entry.debugClass, entry.debugPriority,
|
||||
entry.file, entry.line, entry.message);
|
||||
for (const auto& entry : m_startupEntries) {
|
||||
cb->processEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,9 +445,9 @@ public:
|
||||
PauseThread pause(this);
|
||||
m_logPriority = p;
|
||||
m_logClass = c;
|
||||
for (auto cb : m_consoleCallbacks) {
|
||||
cb->setLogLevels(c, p);
|
||||
}
|
||||
for (auto cb : m_consoleCallbacks) {
|
||||
cb->setLogLevels(c, p);
|
||||
}
|
||||
}
|
||||
|
||||
bool would_log( sgDebugClass c, sgDebugPriority p ) const
|
||||
@@ -551,14 +465,17 @@ public:
|
||||
}
|
||||
|
||||
void log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
const char* fileName, int line, const std::string& msg,
|
||||
bool freeFilename)
|
||||
{
|
||||
p = translatePriority(p);
|
||||
auto tp = translatePriority(p);
|
||||
if (!m_fileLine) {
|
||||
/* This prevents output of file:line in StderrLogCallback. */
|
||||
line = -line;
|
||||
}
|
||||
LogEntry entry(c, p, fileName, line, msg);
|
||||
|
||||
simgear::LogEntry entry(c, tp, p, fileName, line, msg);
|
||||
entry.freeFilename = freeFilename;
|
||||
m_entries.push(entry);
|
||||
}
|
||||
|
||||
@@ -589,8 +506,8 @@ logstream::logstream()
|
||||
|
||||
logstream::~logstream()
|
||||
{
|
||||
popup_msgs.clear();
|
||||
d->stop();
|
||||
d.reset();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -625,7 +542,14 @@ void
|
||||
logstream::log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
{
|
||||
d->log(c, p, fileName, line, msg);
|
||||
d->log(c, p, fileName, line, msg, false);
|
||||
}
|
||||
|
||||
void
|
||||
logstream::logCopyingFilename( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg)
|
||||
{
|
||||
d->log(c, p, strdup(fileName), line, msg, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -686,17 +610,18 @@ void logstream::hexdump(sgDebugClass c, sgDebugPriority p, const char* fileName,
|
||||
void
|
||||
logstream::popup( const std::string& msg)
|
||||
{
|
||||
popup_msgs.push_back(msg);
|
||||
std::lock_guard<std::mutex> g(d->m_lock);
|
||||
d->_popupMessages.push_back(msg);
|
||||
}
|
||||
|
||||
std::string
|
||||
logstream::get_popup()
|
||||
{
|
||||
std::string rv = "";
|
||||
if (!popup_msgs.empty())
|
||||
{
|
||||
rv = popup_msgs.front();
|
||||
popup_msgs.erase(popup_msgs.begin());
|
||||
std::string rv;
|
||||
std::lock_guard<std::mutex> g(d->m_lock);
|
||||
if (!d->_popupMessages.empty()) {
|
||||
rv = d->_popupMessages.front();
|
||||
d->_popupMessages.erase(d->_popupMessages.begin());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -704,7 +629,8 @@ logstream::get_popup()
|
||||
bool
|
||||
logstream::has_popup()
|
||||
{
|
||||
return (popup_msgs.size() > 0) ? true : false;
|
||||
std::lock_guard<std::mutex> g(d->m_lock);
|
||||
return !d->_popupMessages.empty();
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -737,6 +663,16 @@ logstream::set_log_classes( sgDebugClass c)
|
||||
d->setLogLevels(c, d->m_logPriority);
|
||||
}
|
||||
|
||||
sgDebugPriority logstream::priorityFromString(const std::string& s)
|
||||
{
|
||||
if (s == "bulk") return SG_BULK;
|
||||
if (s == "debug") return SG_DEBUG;
|
||||
if (s == "info") return SG_INFO;
|
||||
if (s == "warn") return SG_WARN;
|
||||
if (s == "alert") return SG_ALERT;
|
||||
|
||||
throw std::invalid_argument("Couldn't parse log prioirty:" + s);
|
||||
}
|
||||
|
||||
logstream&
|
||||
sglog()
|
||||
|
||||
@@ -37,27 +37,8 @@ class SGPath;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class LogCallback
|
||||
{
|
||||
public:
|
||||
virtual ~LogCallback() {}
|
||||
virtual void operator()(sgDebugClass c, sgDebugPriority p,
|
||||
const char* file, int line, const std::string& aMessage) = 0;
|
||||
|
||||
void setLogLevels(sgDebugClass c, sgDebugPriority p);
|
||||
protected:
|
||||
LogCallback(sgDebugClass c, sgDebugPriority p);
|
||||
|
||||
bool shouldLog(sgDebugClass c, sgDebugPriority p) const;
|
||||
|
||||
static const char* debugClassToString(sgDebugClass c);
|
||||
static const char* debugPriorityToString(sgDebugPriority p);
|
||||
private:
|
||||
sgDebugClass m_class;
|
||||
sgDebugPriority m_priority;
|
||||
};
|
||||
|
||||
class LogCallback;
|
||||
/**
|
||||
* Helper force a console on platforms where it might optional, when
|
||||
* we need to show a console. This basically means Windows at the
|
||||
@@ -105,6 +86,11 @@ public:
|
||||
|
||||
sgDebugPriority get_log_priority() const;
|
||||
|
||||
/**
|
||||
@brief convert a string value to a log prioirty.
|
||||
throws std::invalid_argument if the string is not valid
|
||||
*/
|
||||
static sgDebugPriority priorityFromString(const std::string& s);
|
||||
/**
|
||||
* set developer mode on/off. In developer mode, SG_DEV_WARN messags
|
||||
* are treated as warnings. In normal (non-developer) mode they are
|
||||
@@ -124,6 +110,14 @@ public:
|
||||
void log( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg);
|
||||
|
||||
// overload of above, which can transfer ownership of the file-name.
|
||||
// this is unecesary overhead when logging from C++, since __FILE__ points
|
||||
// to constant data, but it's needed when the filename is Nasal data (for
|
||||
// example) since during shutdown the filename is freed by Nasal GC
|
||||
// asynchronously with the logging thread.
|
||||
void logCopyingFilename( sgDebugClass c, sgDebugPriority p,
|
||||
const char* fileName, int line, const std::string& msg);
|
||||
|
||||
/**
|
||||
* output formatted hex dump of memory block
|
||||
*/
|
||||
@@ -185,8 +179,6 @@ private:
|
||||
// constructor
|
||||
logstream();
|
||||
|
||||
std::vector<std::string> popup_msgs;
|
||||
|
||||
class LogStreamPrivate;
|
||||
|
||||
std::unique_ptr<LogStreamPrivate> d;
|
||||
|
||||
@@ -45,14 +45,6 @@ namespace simgear
|
||||
std::atomic<int> receiveDepth;
|
||||
std::atomic<int> sentMessageCount;
|
||||
|
||||
void UnlockList()
|
||||
{
|
||||
_lock.unlock();
|
||||
}
|
||||
void LockList()
|
||||
{
|
||||
_lock.lock();
|
||||
}
|
||||
public:
|
||||
Transmitter() : receiveDepth(0), sentMessageCount(0)
|
||||
{
|
||||
@@ -69,20 +61,17 @@ namespace simgear
|
||||
// most recently registered recipients should process the messages/events first.
|
||||
virtual void Register(IReceiver& r)
|
||||
{
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
recipient_list.push_back(&r);
|
||||
r.OnRegisteredAtTransmitter(this);
|
||||
if (std::find(deleted_recipients.begin(), deleted_recipients.end(), &r) != deleted_recipients.end())
|
||||
deleted_recipients.remove(&r);
|
||||
|
||||
UnlockList();
|
||||
}
|
||||
|
||||
// Removes an object from receving message from this transmitter
|
||||
virtual void DeRegister(IReceiver& R)
|
||||
{
|
||||
LockList();
|
||||
//printf("Remove %x\n", &R);
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
if (recipient_list.size())
|
||||
{
|
||||
if (std::find(recipient_list.begin(), recipient_list.end(), &R) != recipient_list.end())
|
||||
@@ -93,7 +82,6 @@ namespace simgear
|
||||
deleted_recipients.push_back(&R);
|
||||
}
|
||||
}
|
||||
UnlockList();
|
||||
}
|
||||
|
||||
// Notify all registered recipients. Stop when receipt status of abort or finished are received.
|
||||
@@ -107,69 +95,68 @@ namespace simgear
|
||||
ReceiptStatus return_status = ReceiptStatusNotProcessed;
|
||||
|
||||
sentMessageCount++;
|
||||
try
|
||||
|
||||
std::vector<IReceiver*> temp;
|
||||
{
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
if (receiveDepth == 0)
|
||||
deleted_recipients.clear();
|
||||
receiveDepth++;
|
||||
std::vector<IReceiver*> temp(recipient_list.size());
|
||||
|
||||
int idx = 0;
|
||||
for (RecipientList::iterator i = recipient_list.begin(); i != recipient_list.end(); i++)
|
||||
{
|
||||
temp[idx++] = *i;
|
||||
temp.push_back(*i);
|
||||
}
|
||||
UnlockList();
|
||||
int tempSize = temp.size();
|
||||
for (int index = 0; index < tempSize; index++)
|
||||
}
|
||||
int tempSize = temp.size();
|
||||
for (int index = 0; index < tempSize; index++)
|
||||
{
|
||||
IReceiver* R = temp[index];
|
||||
{
|
||||
IReceiver* R = temp[index];
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
if (deleted_recipients.size())
|
||||
{
|
||||
if (std::find(deleted_recipients.begin(), deleted_recipients.end(), R) != deleted_recipients.end())
|
||||
{
|
||||
UnlockList();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
UnlockList();
|
||||
if (R)
|
||||
{
|
||||
ReceiptStatus rstat = R->Receive(M);
|
||||
switch (rstat)
|
||||
{
|
||||
case ReceiptStatusFail:
|
||||
return_status = ReceiptStatusFail;
|
||||
break;
|
||||
case ReceiptStatusPending:
|
||||
return_status = ReceiptStatusPending;
|
||||
break;
|
||||
case ReceiptStatusPendingFinished:
|
||||
return rstat;
|
||||
|
||||
case ReceiptStatusNotProcessed:
|
||||
break;
|
||||
case ReceiptStatusOK:
|
||||
if (return_status == ReceiptStatusNotProcessed)
|
||||
return_status = rstat;
|
||||
break;
|
||||
|
||||
case ReceiptStatusAbort:
|
||||
return ReceiptStatusAbort;
|
||||
|
||||
case ReceiptStatusFinished:
|
||||
return ReceiptStatusOK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (R)
|
||||
{
|
||||
ReceiptStatus rstat = R->Receive(M);
|
||||
switch (rstat)
|
||||
{
|
||||
case ReceiptStatusFail:
|
||||
return_status = ReceiptStatusFail;
|
||||
break;
|
||||
|
||||
case ReceiptStatusPending:
|
||||
return_status = ReceiptStatusPending;
|
||||
break;
|
||||
|
||||
case ReceiptStatusPendingFinished:
|
||||
return rstat;
|
||||
|
||||
case ReceiptStatusNotProcessed:
|
||||
break;
|
||||
|
||||
case ReceiptStatusOK:
|
||||
if (return_status == ReceiptStatusNotProcessed)
|
||||
return_status = rstat;
|
||||
break;
|
||||
|
||||
case ReceiptStatusAbort:
|
||||
return ReceiptStatusAbort;
|
||||
|
||||
case ReceiptStatusFinished:
|
||||
return ReceiptStatusOK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw;
|
||||
// return_status = ReceiptStatusAbort;
|
||||
}
|
||||
|
||||
receiveDepth--;
|
||||
return return_status;
|
||||
}
|
||||
@@ -177,9 +164,8 @@ namespace simgear
|
||||
// number of currently registered recipients
|
||||
virtual int Count()
|
||||
{
|
||||
LockList();
|
||||
std::lock_guard<std::mutex> scopeLock(_lock);
|
||||
return recipient_list.size();
|
||||
UnlockList();
|
||||
}
|
||||
|
||||
// number of sent messages.
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include <sstream>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/math/sg_random.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "metar.hxx"
|
||||
@@ -631,30 +632,41 @@ bool SGMetar::scanWind()
|
||||
int dir;
|
||||
if (!strncmp(m, "VRB", 3))
|
||||
m += 3, dir = -1;
|
||||
else if (!strncmp(m, "///", 3)) // direction not measurable
|
||||
m += 3, dir = -1;
|
||||
else if (!scanNumber(&m, &dir, 3))
|
||||
return false;
|
||||
|
||||
int i;
|
||||
if (!scanNumber(&m, &i, 2, 3))
|
||||
if (!strncmp(m, "//", 2)) // speed not measurable
|
||||
m += 2, i = -1;
|
||||
else if (!scanNumber(&m, &i, 2, 3))
|
||||
return false;
|
||||
double speed = i;
|
||||
|
||||
double gust = NaN;
|
||||
if (*m == 'G') {
|
||||
m++;
|
||||
if (!scanNumber(&m, &i, 2, 3))
|
||||
if (!strncmp(m, "//", 2)) // speed not measurable
|
||||
m += 2, i = -1;
|
||||
else if (!scanNumber(&m, &i, 2, 3))
|
||||
return false;
|
||||
gust = i;
|
||||
|
||||
if (i != -1)
|
||||
gust = i;
|
||||
}
|
||||
|
||||
double factor;
|
||||
if (!strncmp(m, "KT", 2))
|
||||
m += 2, factor = SG_KT_TO_MPS;
|
||||
else if (!strncmp(m, "KMH", 3))
|
||||
else if (!strncmp(m, "KMH", 3)) // invalid Km/h
|
||||
m += 3, factor = SG_KMH_TO_MPS;
|
||||
else if (!strncmp(m, "KPH", 3)) // ??
|
||||
else if (!strncmp(m, "KPH", 3)) // invalid Km/h
|
||||
m += 3, factor = SG_KMH_TO_MPS;
|
||||
else if (!strncmp(m, "MPS", 3))
|
||||
m += 3, factor = 1.0;
|
||||
else if (!strncmp(m, " ", 1)) // default to Knots
|
||||
factor = SG_KT_TO_MPS;
|
||||
else
|
||||
return false;
|
||||
if (!scanBoundary(&m))
|
||||
@@ -674,18 +686,28 @@ bool SGMetar::scanVariability()
|
||||
{
|
||||
char *m = _m;
|
||||
int from, to;
|
||||
if (!scanNumber(&m, &from, 3))
|
||||
|
||||
if (!strncmp(m, "///", 3)) // direction not measurable
|
||||
m += 3, from = -1;
|
||||
else if (!scanNumber(&m, &from, 3))
|
||||
return false;
|
||||
|
||||
if (*m++ != 'V')
|
||||
return false;
|
||||
if (!scanNumber(&m, &to, 3))
|
||||
|
||||
if (!strncmp(m, "///", 3)) // direction not measurable
|
||||
m += 3, to = -1;
|
||||
else if (!scanNumber(&m, &to, 3))
|
||||
return false;
|
||||
|
||||
if (!scanBoundary(&m))
|
||||
return false;
|
||||
|
||||
_m = m;
|
||||
_wind_range_from = from;
|
||||
_wind_range_to = to;
|
||||
_grpcount++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -952,6 +974,8 @@ bool SGMetar::scanWeather()
|
||||
weather += string(a->text) + " ";
|
||||
if (!strcmp(a->id, "RA"))
|
||||
_rain = w.intensity;
|
||||
else if (!strcmp(a->id, "DZ"))
|
||||
_rain = LIGHT;
|
||||
else if (!strcmp(a->id, "HA"))
|
||||
_hail = w.intensity;
|
||||
else if (!strcmp(a->id, "SN"))
|
||||
@@ -1045,9 +1069,12 @@ bool SGMetar::scanSkyCondition()
|
||||
return false;
|
||||
m += i;
|
||||
|
||||
if (!strncmp(m, "///", 3)) // vis not measurable (e.g. because of heavy snowing)
|
||||
if (!strncmp(m, "///", 3)) { // vis not measurable (e.g. because of heavy snowing)
|
||||
m += 3, i = -1;
|
||||
else if (scanBoundary(&m)) {
|
||||
sg_srandom_time();
|
||||
// randomize the base height to avoid the black sky issue
|
||||
i = 50 + static_cast<int>(sg_random() * 250.0); // range [5,000, 30,000]
|
||||
} else if (scanBoundary(&m)) {
|
||||
_m = m;
|
||||
return true; // ignore single OVC/BKN/...
|
||||
} else if (!scanNumber(&m, &i, 3))
|
||||
|
||||
@@ -76,12 +76,38 @@ void test_sensor_failure_cloud()
|
||||
SG_CHECK_EQUAL_EP2(m1.getPressure_hPa(), 1025, TEST_EPSILON);
|
||||
}
|
||||
|
||||
void test_sensor_failure_wind()
|
||||
{
|
||||
SGMetar m1("2020/10/23 16:55 LIVD 231655Z /////KT 9999 OVC025 10/08 Q1020 RMK OVC VIS MIN 9999 BLU");
|
||||
SG_CHECK_EQUAL(m1.getWindDir(), -1);
|
||||
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), -1, TEST_EPSILON);
|
||||
|
||||
SGMetar m2("2020/10/21 16:55 LIVD 211655Z /////KT CAVOK 07/03 Q1023 RMK SKC VIS MIN 9999 BLU");
|
||||
SG_CHECK_EQUAL(m2.getWindDir(), -1);
|
||||
SG_CHECK_EQUAL_EP2(m2.getWindSpeed_kt(), -1, TEST_EPSILON);
|
||||
|
||||
SGMetar m3("2020/11/17 16:00 CYAZ 171600Z 14040G//KT 10SM -RA OVC012 12/11 A2895 RMK NS8 VIA CYXY SLP806 DENSITY ALT 900FT");
|
||||
SG_CHECK_EQUAL(m3.getWindDir(), 140);
|
||||
SG_CHECK_EQUAL_EP2(m3.getWindSpeed_kt(), 40, TEST_EPSILON);
|
||||
SG_CHECK_EQUAL_EP2(m3.getGustSpeed_kt(), SGMetarNaN, TEST_EPSILON);
|
||||
}
|
||||
|
||||
void test_wind_unit_not_specified()
|
||||
{
|
||||
SGMetar m1("2020/10/23 11:58 KLSV 231158Z 05010G14 10SM CLR 16/M04 A2992 RMK SLPNO WND DATA ESTMD ALSTG/SLP ESTMD 10320 20124 5//// $");
|
||||
SG_CHECK_EQUAL(m1.getWindDir(), 50);
|
||||
SG_CHECK_EQUAL_EP2(m1.getWindSpeed_kt(), 10.0, TEST_EPSILON);
|
||||
SG_CHECK_EQUAL_EP2(m1.getGustSpeed_kt(), 14.0, TEST_EPSILON);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try {
|
||||
test_basic();
|
||||
test_sensor_failure_weather();
|
||||
test_sensor_failure_cloud();
|
||||
test_sensor_failure_wind();
|
||||
test_wind_unit_not_specified();
|
||||
} catch (sg_exception& e) {
|
||||
cerr << "got exception:" << e.getMessage() << endl;
|
||||
return -1;
|
||||
|
||||
90
simgear/io/ArchiveExtractor_private.hxx
Normal file
90
simgear/io/ArchiveExtractor_private.hxx
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2021 James Turner - <james@flightgear.org>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "untar.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class ArchiveExtractorPrivate
|
||||
{
|
||||
public:
|
||||
ArchiveExtractorPrivate(ArchiveExtractor* o) : outer(o)
|
||||
{
|
||||
assert(outer);
|
||||
}
|
||||
|
||||
virtual ~ArchiveExtractorPrivate() = default;
|
||||
|
||||
typedef enum {
|
||||
INVALID = 0,
|
||||
READING_HEADER,
|
||||
READING_FILE,
|
||||
READING_PADDING,
|
||||
READING_PAX_GLOBAL_ATTRIBUTES,
|
||||
READING_PAX_FILE_ATTRIBUTES,
|
||||
PRE_END_OF_ARCHVE,
|
||||
END_OF_ARCHIVE,
|
||||
ERROR_STATE, ///< states above this are error conditions
|
||||
BAD_ARCHIVE,
|
||||
BAD_DATA,
|
||||
FILTER_STOPPED
|
||||
} State;
|
||||
|
||||
State state = INVALID;
|
||||
ArchiveExtractor* outer = nullptr;
|
||||
|
||||
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
|
||||
|
||||
virtual void flush() = 0;
|
||||
|
||||
SGPath extractRootPath()
|
||||
{
|
||||
return outer->_rootPath;
|
||||
}
|
||||
|
||||
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
|
||||
{
|
||||
return outer->filterPath(pathToExtract);
|
||||
}
|
||||
|
||||
|
||||
bool isSafePath(const std::string& p) const
|
||||
{
|
||||
if (p.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject absolute paths
|
||||
if (p.at(0) == '/') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject paths containing '..'
|
||||
size_t doubleDot = p.find("..");
|
||||
if (doubleDot != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// on POSIX could use realpath to sanity check
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
@@ -35,10 +35,12 @@ set(SOURCES
|
||||
sg_socket.cxx
|
||||
sg_socket_udp.cxx
|
||||
HTTPClient.cxx
|
||||
HTTPTestApi_private.hxx
|
||||
HTTPFileRequest.cxx
|
||||
HTTPMemoryRequest.cxx
|
||||
HTTPRequest.cxx
|
||||
HTTPRepository.cxx
|
||||
HTTPRepository_private.hxx
|
||||
untar.cxx
|
||||
)
|
||||
|
||||
@@ -81,6 +83,7 @@ add_test(binobj ${EXECUTABLE_OUTPUT_PATH}/test_binobj)
|
||||
|
||||
add_executable(test_repository test_repository.cxx)
|
||||
target_link_libraries(test_repository ${TEST_LIBS})
|
||||
target_compile_definitions(test_repository PUBLIC BUILDING_TESTSUITE)
|
||||
add_test(http_repository ${EXECUTABLE_OUTPUT_PATH}/test_repository)
|
||||
|
||||
add_executable(test_untar test_untar.cxx)
|
||||
|
||||
@@ -61,6 +61,10 @@ public:
|
||||
|
||||
struct dns_ctx * ctx;
|
||||
static size_t instanceCounter;
|
||||
|
||||
using RequestVec = std::vector<Request_ptr>;
|
||||
|
||||
RequestVec _activeRequests;
|
||||
};
|
||||
|
||||
size_t Client::ClientPrivate::instanceCounter = 0;
|
||||
@@ -78,6 +82,11 @@ Request::~Request()
|
||||
{
|
||||
}
|
||||
|
||||
void Request::cancel()
|
||||
{
|
||||
_cancelled = true;
|
||||
}
|
||||
|
||||
bool Request::isTimeout() const
|
||||
{
|
||||
return (time(NULL) - _start) > _timeout_secs;
|
||||
@@ -114,18 +123,20 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
|
||||
{
|
||||
SRVRequest * r = static_cast<SRVRequest*>(data);
|
||||
if (result) {
|
||||
r->cname = result->dnssrv_cname;
|
||||
r->qname = result->dnssrv_qname;
|
||||
r->ttl = result->dnssrv_ttl;
|
||||
for (int i = 0; i < result->dnssrv_nrr; i++) {
|
||||
SRVRequest::SRV_ptr srv(new SRVRequest::SRV);
|
||||
r->entries.push_back(srv);
|
||||
srv->priority = result->dnssrv_srv[i].priority;
|
||||
srv->weight = result->dnssrv_srv[i].weight;
|
||||
srv->port = result->dnssrv_srv[i].port;
|
||||
srv->target = result->dnssrv_srv[i].name;
|
||||
if (!r->isCancelled()) {
|
||||
r->cname = result->dnssrv_cname;
|
||||
r->qname = result->dnssrv_qname;
|
||||
r->ttl = result->dnssrv_ttl;
|
||||
for (int i = 0; i < result->dnssrv_nrr; i++) {
|
||||
SRVRequest::SRV_ptr srv(new SRVRequest::SRV);
|
||||
r->entries.push_back(srv);
|
||||
srv->priority = result->dnssrv_srv[i].priority;
|
||||
srv->weight = result->dnssrv_srv[i].weight;
|
||||
srv->port = result->dnssrv_srv[i].port;
|
||||
srv->target = result->dnssrv_srv[i].name;
|
||||
}
|
||||
std::sort(r->entries.begin(), r->entries.end(), sortSRV);
|
||||
}
|
||||
std::sort( r->entries.begin(), r->entries.end(), sortSRV );
|
||||
free(result);
|
||||
}
|
||||
r->setComplete();
|
||||
@@ -134,11 +145,16 @@ static void dnscbSRV(struct dns_ctx *ctx, struct dns_rr_srv *result, void *data)
|
||||
void SRVRequest::submit( Client * client )
|
||||
{
|
||||
// if service is defined, pass service and protocol
|
||||
if (!dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(), _service.empty() ? NULL : _protocol.c_str(), 0, dnscbSRV, this )) {
|
||||
auto q = dns_submit_srv(client->d->ctx, getDn().c_str(), _service.empty() ? NULL : _service.c_str(),
|
||||
_service.empty() ? NULL : _protocol.c_str(),
|
||||
0, dnscbSRV, this);
|
||||
|
||||
if (!q) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
|
||||
return;
|
||||
}
|
||||
_start = time(NULL);
|
||||
_query = q;
|
||||
}
|
||||
|
||||
TXTRequest::TXTRequest( const std::string & dn ) :
|
||||
@@ -151,17 +167,24 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
|
||||
{
|
||||
TXTRequest * r = static_cast<TXTRequest*>(data);
|
||||
if (result) {
|
||||
r->cname = result->dnstxt_cname;
|
||||
r->qname = result->dnstxt_qname;
|
||||
r->ttl = result->dnstxt_ttl;
|
||||
for (int i = 0; i < result->dnstxt_nrr; i++) {
|
||||
//TODO: interprete the .len field of dnstxt_txt?
|
||||
string txt = string((char*)result->dnstxt_txt[i].txt);
|
||||
r->entries.push_back( txt );
|
||||
string_list tokens = simgear::strutils::split( txt, "=", 1 );
|
||||
if( tokens.size() == 2 ) {
|
||||
r->attributes[tokens[0]] = tokens[1];
|
||||
}
|
||||
if (!r->isCancelled()) {
|
||||
r->cname = result->dnstxt_cname;
|
||||
r->qname = result->dnstxt_qname;
|
||||
r->ttl = result->dnstxt_ttl;
|
||||
for (int i = 0; i < result->dnstxt_nrr; i++) {
|
||||
//TODO: interprete the .len field of dnstxt_txt?
|
||||
auto rawTxt = reinterpret_cast<char*>(result->dnstxt_txt[i].txt);
|
||||
if (!rawTxt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const string txt{rawTxt};
|
||||
r->entries.push_back(txt);
|
||||
string_list tokens = simgear::strutils::split(txt, "=", 1);
|
||||
if (tokens.size() == 2) {
|
||||
r->attributes[tokens[0]] = tokens[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
free(result);
|
||||
}
|
||||
@@ -171,11 +194,13 @@ static void dnscbTXT(struct dns_ctx *ctx, struct dns_rr_txt *result, void *data)
|
||||
void TXTRequest::submit( Client * client )
|
||||
{
|
||||
// protocol and service an already encoded in DN so pass in NULL for both
|
||||
if (!dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this )) {
|
||||
auto q = dns_submit_txt(client->d->ctx, getDn().c_str(), DNS_C_IN, 0, dnscbTXT, this);
|
||||
if (!q) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
|
||||
return;
|
||||
}
|
||||
_start = time(NULL);
|
||||
_query = q;
|
||||
}
|
||||
|
||||
|
||||
@@ -190,27 +215,29 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
|
||||
{
|
||||
NAPTRRequest * r = static_cast<NAPTRRequest*>(data);
|
||||
if (result) {
|
||||
r->cname = result->dnsnaptr_cname;
|
||||
r->qname = result->dnsnaptr_qname;
|
||||
r->ttl = result->dnsnaptr_ttl;
|
||||
for (int i = 0; i < result->dnsnaptr_nrr; i++) {
|
||||
if( !r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service )
|
||||
continue;
|
||||
if (!r->isCancelled()) {
|
||||
r->cname = result->dnsnaptr_cname;
|
||||
r->qname = result->dnsnaptr_qname;
|
||||
r->ttl = result->dnsnaptr_ttl;
|
||||
for (int i = 0; i < result->dnsnaptr_nrr; i++) {
|
||||
if (!r->qservice.empty() && r->qservice != result->dnsnaptr_naptr[i].service)
|
||||
continue;
|
||||
|
||||
//TODO: case ignore and result flags may have more than one flag
|
||||
if( !r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags )
|
||||
continue;
|
||||
//TODO: case ignore and result flags may have more than one flag
|
||||
if (!r->qflags.empty() && r->qflags != result->dnsnaptr_naptr[i].flags)
|
||||
continue;
|
||||
|
||||
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
|
||||
r->entries.push_back(naptr);
|
||||
naptr->order = result->dnsnaptr_naptr[i].order;
|
||||
naptr->preference = result->dnsnaptr_naptr[i].preference;
|
||||
naptr->flags = result->dnsnaptr_naptr[i].flags;
|
||||
naptr->service = result->dnsnaptr_naptr[i].service;
|
||||
naptr->regexp = result->dnsnaptr_naptr[i].regexp;
|
||||
naptr->replacement = result->dnsnaptr_naptr[i].replacement;
|
||||
NAPTRRequest::NAPTR_ptr naptr(new NAPTRRequest::NAPTR);
|
||||
r->entries.push_back(naptr);
|
||||
naptr->order = result->dnsnaptr_naptr[i].order;
|
||||
naptr->preference = result->dnsnaptr_naptr[i].preference;
|
||||
naptr->flags = result->dnsnaptr_naptr[i].flags;
|
||||
naptr->service = result->dnsnaptr_naptr[i].service;
|
||||
naptr->regexp = result->dnsnaptr_naptr[i].regexp;
|
||||
naptr->replacement = result->dnsnaptr_naptr[i].replacement;
|
||||
}
|
||||
std::sort(r->entries.begin(), r->entries.end(), sortNAPTR);
|
||||
}
|
||||
std::sort( r->entries.begin(), r->entries.end(), sortNAPTR );
|
||||
free(result);
|
||||
}
|
||||
r->setComplete();
|
||||
@@ -218,11 +245,13 @@ static void dnscbNAPTR(struct dns_ctx *ctx, struct dns_rr_naptr *result, void *d
|
||||
|
||||
void NAPTRRequest::submit( Client * client )
|
||||
{
|
||||
if (!dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this )) {
|
||||
auto q = dns_submit_naptr(client->d->ctx, getDn().c_str(), 0, dnscbNAPTR, this);
|
||||
if (!q) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Can't submit dns request for " << getDn());
|
||||
return;
|
||||
}
|
||||
_start = time(NULL);
|
||||
_query = q;
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +266,7 @@ Client::Client() :
|
||||
|
||||
void Client::makeRequest(const Request_ptr& r)
|
||||
{
|
||||
d->_activeRequests.push_back(r);
|
||||
r->submit(this);
|
||||
}
|
||||
|
||||
@@ -247,6 +277,19 @@ void Client::update(int waitTimeout)
|
||||
return;
|
||||
|
||||
dns_ioevent(d->ctx, now);
|
||||
|
||||
// drop our owning ref to completed requests,
|
||||
// and cancel any which timed out
|
||||
auto it = std::remove_if(d->_activeRequests.begin(), d->_activeRequests.end(),
|
||||
[this](const Request_ptr& r) {
|
||||
if (r->isTimeout()) {
|
||||
dns_cancel(d->ctx, reinterpret_cast<struct dns_query*>(r->_query));
|
||||
return true;
|
||||
}
|
||||
|
||||
return r->isComplete();
|
||||
});
|
||||
d->_activeRequests.erase(it, d->_activeRequests.end());
|
||||
}
|
||||
|
||||
} // of namespace DNS
|
||||
|
||||
@@ -40,28 +40,38 @@ namespace DNS
|
||||
{
|
||||
|
||||
class Client;
|
||||
|
||||
using UDNSQueryPtr = void*;
|
||||
|
||||
class Request : public SGReferenced
|
||||
{
|
||||
public:
|
||||
Request( const std::string & dn );
|
||||
virtual ~Request();
|
||||
std::string getDn() const { return _dn; }
|
||||
const std::string& getDn() const { return _dn; }
|
||||
int getType() const { return _type; }
|
||||
bool isComplete() const { return _complete; }
|
||||
bool isTimeout() const;
|
||||
void setComplete( bool b = true ) { _complete = b; }
|
||||
bool isCancelled() const { return _cancelled; }
|
||||
|
||||
virtual void submit( Client * client) = 0;
|
||||
|
||||
void cancel();
|
||||
|
||||
std::string cname;
|
||||
std::string qname;
|
||||
unsigned ttl;
|
||||
protected:
|
||||
friend class Client;
|
||||
|
||||
UDNSQueryPtr _query = nullptr;
|
||||
std::string _dn;
|
||||
int _type;
|
||||
bool _complete;
|
||||
time_t _timeout_secs;
|
||||
time_t _start;
|
||||
bool _cancelled = false;
|
||||
};
|
||||
typedef SGSharedPtr<Request> Request_ptr;
|
||||
|
||||
@@ -69,7 +79,7 @@ class NAPTRRequest : public Request
|
||||
{
|
||||
public:
|
||||
NAPTRRequest( const std::string & dn );
|
||||
virtual void submit( Client * client );
|
||||
void submit(Client* client) override;
|
||||
|
||||
struct NAPTR : SGReferenced {
|
||||
int order;
|
||||
@@ -92,7 +102,7 @@ class SRVRequest : public Request
|
||||
public:
|
||||
SRVRequest( const std::string & dn );
|
||||
SRVRequest( const std::string & dn, const string & service, const string & protocol );
|
||||
virtual void submit( Client * client );
|
||||
void submit(Client* client) override;
|
||||
|
||||
struct SRV : SGReferenced {
|
||||
int priority;
|
||||
@@ -112,7 +122,7 @@ class TXTRequest : public Request
|
||||
{
|
||||
public:
|
||||
TXTRequest( const std::string & dn );
|
||||
virtual void submit( Client * client );
|
||||
void submit(Client* client) override;
|
||||
|
||||
typedef std::vector<string> TXT_list;
|
||||
typedef std::map<std::string,std::string> TXT_Attribute_map;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
|
||||
#include <simgear/simgear_config.h>
|
||||
|
||||
#include <curl/multi.h>
|
||||
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
|
||||
@@ -47,6 +46,9 @@
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "HTTPClient_private.hxx"
|
||||
#include "HTTPTestApi_private.hxx"
|
||||
|
||||
#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
|
||||
#include "version.h"
|
||||
#else
|
||||
@@ -64,62 +66,23 @@ namespace HTTP
|
||||
extern const int DEFAULT_HTTP_PORT = 80;
|
||||
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
class Connection;
|
||||
typedef std::multimap<std::string, Connection*> ConnectionDict;
|
||||
typedef std::list<Request_ptr> RequestList;
|
||||
|
||||
class Client::ClientPrivate
|
||||
{
|
||||
public:
|
||||
CURLM* curlMulti;
|
||||
|
||||
void createCurlMulti()
|
||||
{
|
||||
curlMulti = curl_multi_init();
|
||||
// see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
|
||||
// we request HTTP 1.1 pipelining
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
|
||||
void Client::ClientPrivate::createCurlMulti() {
|
||||
curlMulti = curl_multi_init();
|
||||
// see https://curl.haxx.se/libcurl/c/CURLMOPT_PIPELINING.html
|
||||
// we request HTTP 1.1 pipelining
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_PIPELINING, 1 /* aka CURLPIPE_HTTP1 */);
|
||||
#if (LIBCURL_VERSION_MINOR >= 30)
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long) maxConnections);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH,
|
||||
(long) maxPipelineDepth);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS,
|
||||
(long) maxHostConnections);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_TOTAL_CONNECTIONS,
|
||||
(long)maxConnections);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_PIPELINE_LENGTH,
|
||||
(long)maxPipelineDepth);
|
||||
curl_multi_setopt(curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS,
|
||||
(long)maxHostConnections);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
typedef std::map<Request_ptr, CURL*> RequestCurlMap;
|
||||
RequestCurlMap requests;
|
||||
|
||||
std::string userAgent;
|
||||
std::string proxy;
|
||||
int proxyPort;
|
||||
std::string proxyAuth;
|
||||
unsigned int maxConnections;
|
||||
unsigned int maxHostConnections;
|
||||
unsigned int maxPipelineDepth;
|
||||
|
||||
RequestList pendingRequests;
|
||||
|
||||
SGTimeStamp timeTransferSample;
|
||||
unsigned int bytesTransferred;
|
||||
unsigned int lastTransferRate;
|
||||
uint64_t totalBytesDownloaded;
|
||||
};
|
||||
|
||||
Client::Client() :
|
||||
d(new ClientPrivate)
|
||||
Client::Client()
|
||||
{
|
||||
d->proxyPort = 0;
|
||||
d->maxConnections = 4;
|
||||
d->maxHostConnections = 4;
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
d->timeTransferSample.stamp();
|
||||
d->totalBytesDownloaded = 0;
|
||||
d->maxPipelineDepth = 5;
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
|
||||
static bool didInitCurlGlobal = false;
|
||||
static std::mutex initMutex;
|
||||
|
||||
@@ -129,7 +92,7 @@ Client::Client() :
|
||||
didInitCurlGlobal = true;
|
||||
}
|
||||
|
||||
d->createCurlMulti();
|
||||
reset();
|
||||
}
|
||||
|
||||
Client::~Client()
|
||||
@@ -161,6 +124,27 @@ void Client::setMaxPipelineDepth(unsigned int depth)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Client::reset()
|
||||
{
|
||||
if (d.get()) {
|
||||
curl_multi_cleanup(d->curlMulti);
|
||||
}
|
||||
|
||||
d.reset(new ClientPrivate);
|
||||
|
||||
d->proxyPort = 0;
|
||||
d->maxConnections = 4;
|
||||
d->maxHostConnections = 4;
|
||||
d->bytesTransferred = 0;
|
||||
d->lastTransferRate = 0;
|
||||
d->timeTransferSample.stamp();
|
||||
d->totalBytesDownloaded = 0;
|
||||
d->maxPipelineDepth = 5;
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
d->tlsCertificatePath = SGPath::fromEnv("SIMGEAR_TLS_CERT_PATH");
|
||||
d->createCurlMulti();
|
||||
}
|
||||
|
||||
void Client::update(int waitTimeout)
|
||||
{
|
||||
if (d->requests.empty()) {
|
||||
@@ -171,32 +155,22 @@ void Client::update(int waitTimeout)
|
||||
}
|
||||
|
||||
int remainingActive, messagesInQueue;
|
||||
#if defined(SG_MAC)
|
||||
// Mac 10.8 libCurl lacks this, let's keep compat for now
|
||||
fd_set curlReadFDs, curlWriteFDs, curlErrorFDs;
|
||||
int maxFD;
|
||||
curl_multi_fdset(d->curlMulti,
|
||||
&curlReadFDs,
|
||||
&curlWriteFDs,
|
||||
&curlErrorFDs,
|
||||
&maxFD);
|
||||
|
||||
struct timeval timeout;
|
||||
long t;
|
||||
|
||||
curl_multi_timeout(d->curlMulti, &t);
|
||||
if ((t < 0) || (t > waitTimeout)) {
|
||||
t = waitTimeout;
|
||||
int numFds;
|
||||
CURLMcode mc = curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
|
||||
if (mc != CURLM_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "curl_multi_wait failed:" << curl_multi_strerror(mc));
|
||||
return;
|
||||
}
|
||||
|
||||
timeout.tv_sec = t / 1000;
|
||||
timeout.tv_usec = (t % 1000) * 1000;
|
||||
::select(maxFD, &curlReadFDs, &curlWriteFDs, &curlErrorFDs, &timeout);
|
||||
#else
|
||||
int numFds;
|
||||
curl_multi_wait(d->curlMulti, NULL, 0, waitTimeout, &numFds);
|
||||
#endif
|
||||
curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
mc = curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
if (mc == CURLM_CALL_MULTI_PERFORM) {
|
||||
// we could loop here, but don't want to get blocked
|
||||
// also this shouldn't ocurr in any modern libCurl
|
||||
curl_multi_perform(d->curlMulti, &remainingActive);
|
||||
} else if (mc != CURLM_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "curl_multi_perform failed:" << curl_multi_strerror(mc));
|
||||
return;
|
||||
}
|
||||
|
||||
CURLMsg* msg;
|
||||
while ((msg = curl_multi_info_read(d->curlMulti, &messagesInQueue))) {
|
||||
@@ -221,12 +195,23 @@ void Client::update(int waitTimeout)
|
||||
assert(it->second == e);
|
||||
d->requests.erase(it);
|
||||
|
||||
if (msg->data.result == 0) {
|
||||
req->responseComplete();
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "CURL Result:" << msg->data.result << " " << curl_easy_strerror(msg->data.result));
|
||||
req->setFailure(msg->data.result, curl_easy_strerror(msg->data.result));
|
||||
}
|
||||
bool doProcess = true;
|
||||
if (d->testsuiteResponseDoneCallback) {
|
||||
doProcess =
|
||||
!d->testsuiteResponseDoneCallback(msg->data.result, req);
|
||||
}
|
||||
|
||||
if (doProcess) {
|
||||
if (msg->data.result == 0) {
|
||||
req->responseComplete();
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN,
|
||||
"CURL Result:" << msg->data.result << " "
|
||||
<< curl_easy_strerror(msg->data.result));
|
||||
req->setFailure(msg->data.result,
|
||||
curl_easy_strerror(msg->data.result));
|
||||
}
|
||||
}
|
||||
|
||||
curl_multi_remove_handle(d->curlMulti, e);
|
||||
curl_easy_cleanup(e);
|
||||
@@ -285,6 +270,11 @@ void Client::makeRequest(const Request_ptr& r)
|
||||
|
||||
curl_easy_setopt(curlRequest, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
if (!d->tlsCertificatePath.isNull()) {
|
||||
const auto utf8 = d->tlsCertificatePath.utf8Str();
|
||||
curl_easy_setopt(curlRequest, CURLOPT_CAINFO, utf8.c_str());
|
||||
}
|
||||
|
||||
if (!d->proxy.empty()) {
|
||||
curl_easy_setopt(curlRequest, CURLOPT_PROXY, d->proxy.c_str());
|
||||
curl_easy_setopt(curlRequest, CURLOPT_PROXYPORT, d->proxyPort);
|
||||
@@ -552,6 +542,17 @@ void Client::clearAllConnections()
|
||||
d->createCurlMulti();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
void TestApi::setResponseDoneCallback(Client *cl, ResponseDoneCallback cb) {
|
||||
cl->d->testsuiteResponseDoneCallback = cb;
|
||||
}
|
||||
|
||||
void TestApi::markRequestAsFailed(Request_ptr req, int curlCode,
|
||||
const std::string &message) {
|
||||
req->setFailure(curlCode, message);
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#ifndef SG_HTTP_CLIENT_HXX
|
||||
#define SG_HTTP_CLIENT_HXX
|
||||
|
||||
#include <memory> // for std::unique_ptr
|
||||
#include <functional>
|
||||
#include <memory> // for std::unique_ptr
|
||||
#include <stdint.h> // for uint_64t
|
||||
|
||||
#include <simgear/io/HTTPFileRequest.hxx>
|
||||
@@ -47,6 +48,8 @@ public:
|
||||
|
||||
void update(int waitTimeout = 0);
|
||||
|
||||
void reset();
|
||||
|
||||
void makeRequest(const Request_ptr& r);
|
||||
|
||||
void cancelRequest(const Request_ptr& r, std::string reason = std::string());
|
||||
@@ -123,6 +126,7 @@ private:
|
||||
|
||||
friend class Connection;
|
||||
friend class Request;
|
||||
friend class TestApi;
|
||||
|
||||
class ClientPrivate;
|
||||
std::unique_ptr<ClientPrivate> d;
|
||||
|
||||
68
simgear/io/HTTPClient_private.hxx
Normal file
68
simgear/io/HTTPClient_private.hxx
Normal file
@@ -0,0 +1,68 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
|
||||
#include <curl/multi.h>
|
||||
|
||||
namespace simgear {
|
||||
namespace HTTP {
|
||||
|
||||
typedef std::list<Request_ptr> RequestList;
|
||||
|
||||
using ResponseDoneCallback =
|
||||
std::function<bool(int curlResult, Request_ptr req)>;
|
||||
|
||||
class Client::ClientPrivate {
|
||||
public:
|
||||
CURLM *curlMulti;
|
||||
|
||||
void createCurlMulti();
|
||||
|
||||
typedef std::map<Request_ptr, CURL *> RequestCurlMap;
|
||||
RequestCurlMap requests;
|
||||
|
||||
std::string userAgent;
|
||||
std::string proxy;
|
||||
int proxyPort;
|
||||
std::string proxyAuth;
|
||||
unsigned int maxConnections;
|
||||
unsigned int maxHostConnections;
|
||||
unsigned int maxPipelineDepth;
|
||||
|
||||
RequestList pendingRequests;
|
||||
|
||||
SGTimeStamp timeTransferSample;
|
||||
unsigned int bytesTransferred;
|
||||
unsigned int lastTransferRate;
|
||||
uint64_t totalBytesDownloaded;
|
||||
|
||||
SGPath tlsCertificatePath;
|
||||
|
||||
// only used by unit-tests / test-api, but
|
||||
// only costs us a pointe here to declare it.
|
||||
ResponseDoneCallback testsuiteResponseDoneCallback;
|
||||
};
|
||||
|
||||
} // namespace HTTP
|
||||
|
||||
} // namespace simgear
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
||||
#ifndef SG_IO_HTTP_REPOSITORY_HXX
|
||||
#define SG_IO_HTTP_REPOSITORY_HXX
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
@@ -32,49 +33,85 @@ class HTTPRepoPrivate;
|
||||
class HTTPRepository
|
||||
{
|
||||
public:
|
||||
enum ResultCode {
|
||||
REPO_NO_ERROR = 0,
|
||||
REPO_ERROR_NOT_FOUND,
|
||||
REPO_ERROR_SOCKET,
|
||||
SVN_ERROR_XML,
|
||||
SVN_ERROR_TXDELTA,
|
||||
REPO_ERROR_IO,
|
||||
REPO_ERROR_CHECKSUM,
|
||||
REPO_ERROR_FILE_NOT_FOUND,
|
||||
REPO_ERROR_HTTP,
|
||||
REPO_ERROR_CANCELLED,
|
||||
REPO_PARTIAL_UPDATE
|
||||
enum ResultCode {
|
||||
REPO_NO_ERROR = 0,
|
||||
REPO_ERROR_NOT_FOUND,
|
||||
REPO_ERROR_SOCKET,
|
||||
SVN_ERROR_XML,
|
||||
SVN_ERROR_TXDELTA,
|
||||
REPO_ERROR_IO,
|
||||
REPO_ERROR_CHECKSUM,
|
||||
REPO_ERROR_FILE_NOT_FOUND,
|
||||
REPO_ERROR_HTTP,
|
||||
REPO_ERROR_CANCELLED,
|
||||
REPO_PARTIAL_UPDATE ///< repository is working, but file-level failures
|
||||
///< occurred
|
||||
};
|
||||
|
||||
HTTPRepository(const SGPath &root, HTTP::Client *cl);
|
||||
virtual ~HTTPRepository();
|
||||
|
||||
virtual SGPath fsBase() const;
|
||||
|
||||
virtual void setBaseUrl(const std::string &url);
|
||||
virtual std::string baseUrl() const;
|
||||
|
||||
virtual HTTP::Client *http() const;
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isDoingSync() const;
|
||||
|
||||
/**
|
||||
@brief call this periodically to progress non-network tasks
|
||||
*/
|
||||
void process();
|
||||
|
||||
virtual ResultCode failure() const;
|
||||
|
||||
virtual size_t bytesToDownload() const;
|
||||
|
||||
virtual size_t bytesDownloaded() const;
|
||||
|
||||
virtual size_t bytesToExtract() const;
|
||||
|
||||
/**
|
||||
* optionally provide the location of an installer copy of this
|
||||
* repository. When a file is missing it will be copied from this tree.
|
||||
*/
|
||||
void setInstalledCopyPath(const SGPath ©Path);
|
||||
|
||||
static std::string resultCodeAsString(ResultCode code);
|
||||
|
||||
enum class SyncAction { Add, Update, Delete, UpToDate };
|
||||
|
||||
enum EntryType { FileType, DirectoryType, TarballType };
|
||||
|
||||
struct SyncItem {
|
||||
const std::string directory; // relative path in the repository
|
||||
const EntryType type;
|
||||
const std::string filename;
|
||||
const SyncAction action;
|
||||
const SGPath pathOnDisk; // path the entry does / will have
|
||||
};
|
||||
|
||||
using SyncPredicate = std::function<bool(const SyncItem &item)>;
|
||||
|
||||
void setFilter(SyncPredicate sp);
|
||||
|
||||
struct Failure {
|
||||
SGPath path;
|
||||
ResultCode error;
|
||||
};
|
||||
|
||||
HTTPRepository(const SGPath& root, HTTP::Client* cl);
|
||||
virtual ~HTTPRepository();
|
||||
|
||||
virtual SGPath fsBase() const;
|
||||
|
||||
virtual void setBaseUrl(const std::string& url);
|
||||
virtual std::string baseUrl() const;
|
||||
|
||||
virtual HTTP::Client* http() const;
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isDoingSync() const;
|
||||
|
||||
virtual ResultCode failure() const;
|
||||
|
||||
virtual size_t bytesToDownload() const;
|
||||
|
||||
virtual size_t bytesDownloaded() const;
|
||||
using FailureVec = std::vector<Failure>;
|
||||
|
||||
/**
|
||||
* optionally provide the location of an installer copy of this
|
||||
* repository. When a file is missing it will be copied from this tree.
|
||||
* @brief return file-level failures
|
||||
*/
|
||||
void setInstalledCopyPath(const SGPath& copyPath);
|
||||
|
||||
static std::string resultCodeAsString(ResultCode code);
|
||||
FailureVec failures() const;
|
||||
|
||||
private:
|
||||
private:
|
||||
bool isBare() const;
|
||||
|
||||
std::unique_ptr<HTTPRepoPrivate> _d;
|
||||
|
||||
127
simgear/io/HTTPRepository_private.hxx
Normal file
127
simgear/io/HTTPRepository_private.hxx
Normal file
@@ -0,0 +1,127 @@
|
||||
// HTTPRepository.cxx -- plain HTTP TerraSync remote client
|
||||
//
|
||||
// Copyright (C) 20126 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
// USA.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
|
||||
#include "HTTPRepository.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class HTTPDirectory;
|
||||
using HTTPDirectory_ptr = std::unique_ptr<HTTPDirectory>;
|
||||
|
||||
class HTTPRepoGetRequest : public HTTP::Request {
|
||||
public:
|
||||
HTTPRepoGetRequest(HTTPDirectory *d, const std::string &u)
|
||||
: HTTP::Request(u), _directory(d) {}
|
||||
|
||||
virtual void cancel();
|
||||
|
||||
size_t contentSize() const { return _contentSize; }
|
||||
|
||||
void setContentSize(size_t sz) { _contentSize = sz; }
|
||||
|
||||
protected:
|
||||
HTTPDirectory *_directory;
|
||||
size_t _contentSize = 0;
|
||||
};
|
||||
|
||||
using RepoRequestPtr = SGSharedPtr<HTTPRepoGetRequest>;
|
||||
|
||||
class HTTPRepoPrivate {
|
||||
public:
|
||||
|
||||
|
||||
HTTPRepository::FailureVec failures;
|
||||
int maxPermittedFailures = 16;
|
||||
|
||||
HTTPRepoPrivate(HTTPRepository* parent)
|
||||
: p(parent)
|
||||
{
|
||||
}
|
||||
|
||||
~HTTPRepoPrivate();
|
||||
|
||||
HTTPRepository *p; // link back to outer
|
||||
HTTP::Client* http = nullptr;
|
||||
std::string baseUrl;
|
||||
SGPath basePath;
|
||||
bool isUpdating = false;
|
||||
HTTPRepository::ResultCode status = HTTPRepository::REPO_NO_ERROR;
|
||||
HTTPDirectory_ptr rootDir;
|
||||
size_t totalDownloaded = 0;
|
||||
size_t bytesToExtract = 0;
|
||||
size_t bytesExtracted = 0;
|
||||
HTTPRepository::SyncPredicate syncPredicate;
|
||||
|
||||
HTTP::Request_ptr updateFile(HTTPDirectory *dir, const std::string &name,
|
||||
size_t sz);
|
||||
HTTP::Request_ptr updateDir(HTTPDirectory *dir, const std::string &hash,
|
||||
size_t sz);
|
||||
|
||||
void failedToGetRootIndex(HTTPRepository::ResultCode st);
|
||||
void failedToUpdateChild(const SGPath &relativePath,
|
||||
HTTPRepository::ResultCode fileStatus);
|
||||
|
||||
void updatedChildSuccessfully(const SGPath &relativePath);
|
||||
|
||||
void checkForComplete();
|
||||
|
||||
typedef std::vector<RepoRequestPtr> RequestVector;
|
||||
RequestVector queuedRequests, activeRequests;
|
||||
|
||||
void makeRequest(RepoRequestPtr req);
|
||||
|
||||
enum class RequestFinish { Done, Retry };
|
||||
|
||||
void finishedRequest(const RepoRequestPtr &req, RequestFinish retryRequest);
|
||||
|
||||
HTTPDirectory *getOrCreateDirectory(const std::string &path);
|
||||
bool deleteDirectory(const std::string &relPath, const SGPath &absPath);
|
||||
|
||||
|
||||
typedef std::vector<HTTPDirectory_ptr> DirectoryVector;
|
||||
DirectoryVector directories;
|
||||
|
||||
void scheduleUpdateOfChildren(HTTPDirectory *dir);
|
||||
|
||||
SGPath installedCopyPath;
|
||||
|
||||
int countDirtyHashCaches() const;
|
||||
void flushHashCaches();
|
||||
|
||||
enum ProcessResult { ProcessContinue, ProcessDone, ProcessFailed };
|
||||
|
||||
using RepoProcessTask = std::function<ProcessResult(HTTPRepoPrivate *repo)>;
|
||||
|
||||
void addTask(RepoProcessTask task);
|
||||
|
||||
std::deque<RepoProcessTask> pendingTasks;
|
||||
};
|
||||
|
||||
} // namespace simgear
|
||||
@@ -56,6 +56,15 @@ Request::~Request()
|
||||
|
||||
}
|
||||
|
||||
void Request::prepareForRetry() {
|
||||
setReadyState(UNSENT);
|
||||
_willClose = false;
|
||||
_connectionCloseHeader = false;
|
||||
_responseStatus = 0;
|
||||
_responseLength = 0;
|
||||
_receivedBodyBytes = 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Request* Request::done(const Callback& cb)
|
||||
{
|
||||
@@ -193,13 +202,16 @@ void Request::onDone()
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::onFail()
|
||||
{
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_INFO,
|
||||
"request failed:" << url() << " : "
|
||||
<< responseCode() << "/" << responseReason()
|
||||
);
|
||||
// log if we FAIELD< but not if we CANCELLED
|
||||
if (_ready_state == FAILED) {
|
||||
SG_LOG
|
||||
(
|
||||
SG_IO,
|
||||
SG_INFO,
|
||||
"request failed:" << url() << " : "
|
||||
<< responseCode() << "/" << responseReason()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -344,12 +356,17 @@ void Request::setSuccess(int code)
|
||||
//------------------------------------------------------------------------------
|
||||
void Request::setFailure(int code, const std::string& reason)
|
||||
{
|
||||
// we use -1 for cancellation, don't be noisy in that case
|
||||
if (code >= 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "HTTP request: set failure:" << code << " reason " << reason);
|
||||
}
|
||||
|
||||
_responseStatus = code;
|
||||
_responseReason = reason;
|
||||
|
||||
if( !isComplete() )
|
||||
setReadyState(FAILED);
|
||||
if( !isComplete() ) {
|
||||
setReadyState(code < 0 ? CANCELLED : FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -373,6 +390,12 @@ void Request::setReadyState(ReadyState state)
|
||||
|
||||
_cb_fail(this);
|
||||
}
|
||||
else if (state == CANCELLED )
|
||||
{
|
||||
onFail(); // do this for compatability
|
||||
onAlways();
|
||||
_cb_fail(this);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
@@ -402,7 +425,7 @@ bool Request::serverSupportsPipelining() const
|
||||
//------------------------------------------------------------------------------
|
||||
bool Request::isComplete() const
|
||||
{
|
||||
return _ready_state == DONE || _ready_state == FAILED;
|
||||
return _ready_state == DONE || _ready_state == FAILED || _ready_state == CANCELLED;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -54,7 +54,8 @@ public:
|
||||
HEADERS_RECEIVED,
|
||||
LOADING,
|
||||
DONE,
|
||||
FAILED
|
||||
FAILED,
|
||||
CANCELLED
|
||||
};
|
||||
|
||||
virtual ~Request();
|
||||
@@ -207,7 +208,9 @@ public:
|
||||
*/
|
||||
bool serverSupportsPipelining() const;
|
||||
|
||||
protected:
|
||||
virtual void prepareForRetry();
|
||||
|
||||
protected:
|
||||
Request(const std::string& url, const std::string method = "GET");
|
||||
|
||||
virtual void requestStart();
|
||||
@@ -221,12 +224,14 @@ protected:
|
||||
virtual void onFail();
|
||||
virtual void onAlways();
|
||||
|
||||
void setFailure(int code, const std::string& reason);
|
||||
void setSuccess(int code);
|
||||
private:
|
||||
void setFailure(int code, const std::string &reason);
|
||||
|
||||
private:
|
||||
friend class Client;
|
||||
friend class Connection;
|
||||
friend class ContentDecoder;
|
||||
friend class TestApi;
|
||||
|
||||
Request(const Request&); // = delete;
|
||||
Request& operator=(const Request&); // = delete;
|
||||
|
||||
45
simgear/io/HTTPTestApi_private.hxx
Normal file
45
simgear/io/HTTPTestApi_private.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
namespace simgear {
|
||||
namespace HTTP {
|
||||
|
||||
class Client;
|
||||
|
||||
using ResponseDoneCallback =
|
||||
std::function<bool(int curlResult, Request_ptr req)>;
|
||||
|
||||
/**
|
||||
* @brief this API is for unit-testing HTTP code.
|
||||
* Don't use it for anything else. It's for unit-testing.
|
||||
*/
|
||||
class TestApi {
|
||||
public:
|
||||
// alow test suite to manipulate requests to simulate network errors;
|
||||
// without this, it's hard to provoke certain failures in a loop-back
|
||||
// network sitation.
|
||||
static void setResponseDoneCallback(Client *cl, ResponseDoneCallback cb);
|
||||
|
||||
static void markRequestAsFailed(Request_ptr req, int curlCode,
|
||||
const std::string &message);
|
||||
};
|
||||
|
||||
} // namespace HTTP
|
||||
} // namespace simgear
|
||||
@@ -185,6 +185,9 @@ gzfilebuf::setcompressionstrategy( int comp_strategy )
|
||||
|
||||
z_off_t
|
||||
gzfilebuf::approxOffset() {
|
||||
#ifdef __OpenBSD__
|
||||
z_off_t res = 0;
|
||||
#else
|
||||
z_off_t res = gzoffset(file);
|
||||
|
||||
if (res == -1) {
|
||||
@@ -201,7 +204,7 @@ gzfilebuf::approxOffset() {
|
||||
SG_LOG( SG_GENERAL, SG_ALERT, errMsg );
|
||||
throw sg_io_exception(errMsg);
|
||||
}
|
||||
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/math/SGGeometry.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
@@ -563,6 +564,14 @@ bool SGBinObject::read_bin( const SGPath& file ) {
|
||||
// read headers
|
||||
unsigned int header;
|
||||
sgReadUInt( fp, &header );
|
||||
|
||||
if (sgReadError()) {
|
||||
int code = 0;
|
||||
const char* gzErrorString = gzerror(fp, &code);
|
||||
gzclose(fp);
|
||||
throw sg_io_exception("Unable to read BTG header: " + string{gzErrorString} + ", code =" + std::to_string(code), sg_location(file));
|
||||
}
|
||||
|
||||
if ( ((header & 0xFF000000) >> 24) == 'S' &&
|
||||
((header & 0x00FF0000) >> 16) == 'G' ) {
|
||||
|
||||
|
||||
@@ -71,21 +71,33 @@ SGFile::~SGFile() {
|
||||
std::string SGFile::computeHash()
|
||||
{
|
||||
if (!file_name.exists())
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
simgear::sha1nfo info;
|
||||
sha1_init(&info);
|
||||
char* buf = static_cast<char*>(malloc(1024 * 1024));
|
||||
|
||||
// unique_ptr with custom deleter for exception safety
|
||||
const int bufSize = 1024 * 1024;
|
||||
std::unique_ptr<char, std::function<void(char*)>> buf{static_cast<char*>(malloc(bufSize)),
|
||||
[](char* p) { free(p); }};
|
||||
|
||||
if (!buf) {
|
||||
// @TODO report out of memory error
|
||||
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check:" << file_name);
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t readLen;
|
||||
SGBinaryFile f(file_name);
|
||||
if (!f.open(SG_IO_IN)) {
|
||||
throw sg_io_exception("Couldn't open file for compute hash", file_name);
|
||||
SG_LOG(SG_IO, SG_ALERT, "SGFile::computeHash: Failed to open " << file_name);
|
||||
return {};
|
||||
}
|
||||
while ((readLen = f.read(buf, 1024 * 1024)) > 0) {
|
||||
sha1_write(&info, buf, readLen);
|
||||
while ((readLen = f.read(buf.get(), bufSize)) > 0) {
|
||||
sha1_write(&info, buf.get(), readLen);
|
||||
}
|
||||
|
||||
f.close();
|
||||
free(buf);
|
||||
std::string hashBytes((char*)sha1_result(&info), HASH_LENGTH);
|
||||
return simgear::strutils::encodeHex(hashBytes);
|
||||
}
|
||||
|
||||
BIN
simgear/io/test.tar.xz
Normal file
BIN
simgear/io/test.tar.xz
Normal file
Binary file not shown.
@@ -1,16 +1,18 @@
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <simgear/simgear_config.h>
|
||||
|
||||
#include "test_HTTP.hxx"
|
||||
#include "HTTPRepository.hxx"
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPRepository.hxx"
|
||||
#include "HTTPTestApi_private.hxx"
|
||||
#include "test_HTTP.hxx"
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/misc/sg_hash.hxx>
|
||||
@@ -25,8 +27,14 @@
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
using TestApi = simgear::HTTP::TestApi;
|
||||
|
||||
std::string dataForFile(const std::string& parentName, const std::string& name, int revision)
|
||||
{
|
||||
if (name == "zeroByteFile") {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::ostringstream os;
|
||||
// random content but which definitely depends on our tree location
|
||||
// and revision.
|
||||
@@ -45,6 +53,9 @@ std::string hashForData(const std::string& d)
|
||||
return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
|
||||
}
|
||||
|
||||
class TestRepoEntry;
|
||||
using AccessCallback = std::function<void(TestRepoEntry &entry)>;
|
||||
|
||||
class TestRepoEntry
|
||||
{
|
||||
public:
|
||||
@@ -70,7 +81,8 @@ public:
|
||||
int requestCount;
|
||||
bool getWillFail;
|
||||
bool returnCorruptData;
|
||||
std::unique_ptr<SGCallback> accessCallback;
|
||||
|
||||
AccessCallback accessCallback;
|
||||
|
||||
void clearRequestCounts();
|
||||
|
||||
@@ -270,8 +282,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->accessCallback.get()) {
|
||||
(*entry->accessCallback)();
|
||||
if (entry->accessCallback) {
|
||||
entry->accessCallback(*entry);
|
||||
}
|
||||
|
||||
if (entry->getWillFail) {
|
||||
@@ -282,20 +294,29 @@ public:
|
||||
entry->requestCount++;
|
||||
|
||||
std::string content;
|
||||
bool closeSocket = false;
|
||||
size_t contentSize = 0;
|
||||
|
||||
if (entry->returnCorruptData) {
|
||||
content = dataForFile("!$£$!" + entry->parent->name,
|
||||
"corrupt_" + entry->name,
|
||||
entry->revision);
|
||||
contentSize = content.size();
|
||||
} else {
|
||||
content = entry->data();
|
||||
content = entry->data();
|
||||
contentSize = content.size();
|
||||
}
|
||||
|
||||
std::stringstream d;
|
||||
d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
|
||||
d << "Content-Length:" << content.size() << "\r\n";
|
||||
d << "Content-Length:" << contentSize << "\r\n";
|
||||
d << "\r\n"; // final CRLF to terminate the headers
|
||||
d << content;
|
||||
push(d.str().c_str());
|
||||
|
||||
if (closeSocket) {
|
||||
closeWhenDone();
|
||||
}
|
||||
} else {
|
||||
sendErrorResponse(404, false, "");
|
||||
}
|
||||
@@ -392,6 +413,7 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
|
||||
cl->update();
|
||||
testServer.poll();
|
||||
|
||||
repo->process();
|
||||
if (!repo->isDoingSync()) {
|
||||
return;
|
||||
}
|
||||
@@ -401,6 +423,16 @@ void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
|
||||
std::cerr << "timed out" << std::endl;
|
||||
}
|
||||
|
||||
void runForTime(HTTP::Client *cl, HTTPRepository *repo, int msec = 15) {
|
||||
SGTimeStamp start(SGTimeStamp::now());
|
||||
while (start.elapsedMSec() < msec) {
|
||||
cl->update();
|
||||
testServer.poll();
|
||||
repo->process();
|
||||
SGTimeStamp::sleepForMSec(1);
|
||||
}
|
||||
}
|
||||
|
||||
void testBasicClone(HTTP::Client* cl)
|
||||
{
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
@@ -418,6 +450,7 @@ void testBasicClone(HTTP::Client* cl)
|
||||
verifyFileState(p, "fileA");
|
||||
verifyFileState(p, "dirA/subdirA/fileAAA");
|
||||
verifyFileState(p, "dirC/subdirA/subsubA/fileCAAA");
|
||||
verifyFileState(p, "dirA/subdirA/zeroByteFile");
|
||||
|
||||
global_repo->findEntry("fileA")->revision++;
|
||||
global_repo->findEntry("dirB/subdirA/fileBAA")->revision++;
|
||||
@@ -618,9 +651,19 @@ void testAbandonCorruptFiles(HTTP::Client* cl)
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) {
|
||||
std::cerr << "Got failure state:" << repo->failure() << std::endl;
|
||||
throw sg_exception("Bad result from corrupt files test");
|
||||
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
|
||||
std::cerr << "Got failure state:" << repo->failure() << std::endl;
|
||||
throw sg_exception("Bad result from corrupt files test");
|
||||
}
|
||||
|
||||
auto failedFiles = repo->failures();
|
||||
if (failedFiles.size() != 1) {
|
||||
throw sg_exception("Bad result from corrupt files test");
|
||||
}
|
||||
|
||||
if (failedFiles.front().path.utf8Str() != "dirB/subdirG/fileBGA") {
|
||||
throw sg_exception("Bad path from corrupt files test:" +
|
||||
failedFiles.front().path.utf8Str());
|
||||
}
|
||||
|
||||
repo.reset();
|
||||
@@ -657,15 +700,21 @@ void testServerModifyDuringSync(HTTP::Client* cl)
|
||||
repo.reset(new HTTPRepository(p, cl));
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback.reset(make_callback(&modifyBTree));
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback =
|
||||
[](const TestRepoEntry &r) {
|
||||
std::cout << "Modifying sub-tree" << std::endl;
|
||||
global_repo->findEntry("dirB/subdirA/fileBAC")->revision++;
|
||||
global_repo->defineFile("dirB/subdirZ/fileBZA");
|
||||
global_repo->findEntry("dirB/subdirB/fileBBB")->revision++;
|
||||
};
|
||||
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback.reset();
|
||||
global_repo->findEntry("dirA/fileAA")->accessCallback = AccessCallback{};
|
||||
|
||||
if (repo->failure() != HTTPRepository::REPO_ERROR_CHECKSUM) {
|
||||
throw sg_exception("Bad result from modify during sync test");
|
||||
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
|
||||
throw sg_exception("Bad result from modify during sync test");
|
||||
}
|
||||
|
||||
std::cout << "Passed test modify server during sync" << std::endl;
|
||||
@@ -755,6 +804,124 @@ void testCopyInstalledChildren(HTTP::Client* cl)
|
||||
std::cout << "passed Copy installed children" << std::endl;
|
||||
}
|
||||
|
||||
void testRetryAfterSocketFailure(HTTP::Client *cl) {
|
||||
global_repo->clearRequestCounts();
|
||||
global_repo->clearFailFlags();
|
||||
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
SGPath p(simgear::Dir::current().path());
|
||||
p.append("http_repo_retry_after_socket_fail");
|
||||
simgear::Dir pd(p);
|
||||
if (pd.exists()) {
|
||||
pd.removeChildren();
|
||||
}
|
||||
|
||||
repo.reset(new HTTPRepository(p, cl));
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
|
||||
int aaFailsRemaining = 2;
|
||||
int subdirBAFailsRemaining = 2;
|
||||
TestApi::setResponseDoneCallback(
|
||||
cl, [&aaFailsRemaining, &subdirBAFailsRemaining](int curlResult,
|
||||
HTTP::Request_ptr req) {
|
||||
if (req->url() == "http://localhost:2000/repo/dirA/fileAA") {
|
||||
if (aaFailsRemaining == 0)
|
||||
return false;
|
||||
|
||||
--aaFailsRemaining;
|
||||
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
|
||||
return true;
|
||||
} else if (req->url() ==
|
||||
"http://localhost:2000/repo/dirB/subdirA/.dirindex") {
|
||||
if (subdirBAFailsRemaining == 0)
|
||||
return false;
|
||||
|
||||
--subdirBAFailsRemaining;
|
||||
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
|
||||
if (repo->failure() != HTTPRepository::REPO_NO_ERROR) {
|
||||
throw sg_exception("Bad result from retry socket failure test");
|
||||
}
|
||||
|
||||
verifyFileState(p, "dirA/fileAA");
|
||||
verifyFileState(p, "dirB/subdirA/fileBAA");
|
||||
verifyFileState(p, "dirB/subdirA/fileBAC");
|
||||
|
||||
verifyRequestCount("dirA/fileAA", 3);
|
||||
verifyRequestCount("dirB/subdirA", 3);
|
||||
verifyRequestCount("dirB/subdirA/fileBAC", 1);
|
||||
}
|
||||
|
||||
void testPersistentSocketFailure(HTTP::Client *cl) {
|
||||
global_repo->clearRequestCounts();
|
||||
global_repo->clearFailFlags();
|
||||
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
SGPath p(simgear::Dir::current().path());
|
||||
p.append("http_repo_persistent_socket_fail");
|
||||
simgear::Dir pd(p);
|
||||
if (pd.exists()) {
|
||||
pd.removeChildren();
|
||||
}
|
||||
|
||||
repo.reset(new HTTPRepository(p, cl));
|
||||
repo->setBaseUrl("http://localhost:2000/repo");
|
||||
|
||||
TestApi::setResponseDoneCallback(
|
||||
cl, [](int curlResult, HTTP::Request_ptr req) {
|
||||
const auto url = req->url();
|
||||
if (url.find("http://localhost:2000/repo/dirB") == 0) {
|
||||
TestApi::markRequestAsFailed(req, 56, "Simulated socket failure");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
repo->update();
|
||||
waitForUpdateComplete(cl, repo.get());
|
||||
|
||||
if (repo->failure() != HTTPRepository::REPO_PARTIAL_UPDATE) {
|
||||
throw sg_exception("Bad result from retry socket failure test");
|
||||
}
|
||||
|
||||
verifyFileState(p, "dirA/fileAA");
|
||||
verifyRequestCount("dirA/fileAA", 1);
|
||||
|
||||
verifyRequestCount("dirD/fileDA", 1);
|
||||
verifyRequestCount("dirD/subdirDA/fileDAA", 1);
|
||||
verifyRequestCount("dirD/subdirDB/fileDBA", 1);
|
||||
}
|
||||
|
||||
void testHashOnEmptyFile()
|
||||
{
|
||||
std::unique_ptr<HTTPRepository> repo;
|
||||
SGPath p(simgear::Dir::current().path());
|
||||
p.append("sgfile_compute_hash");
|
||||
simgear::Dir pd(p);
|
||||
if (pd.exists()) {
|
||||
pd.removeChildren();
|
||||
} else {
|
||||
pd.create(0700);
|
||||
}
|
||||
|
||||
SGPath fPath = p / "test_empty_file";
|
||||
|
||||
SGFile file(fPath);
|
||||
file.open(SG_IO_OUT);
|
||||
file.close();
|
||||
|
||||
const auto hash = file.computeHash();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
sglog().setLogLevels( SG_ALL, SG_INFO );
|
||||
@@ -770,6 +937,8 @@ int main(int argc, char* argv[])
|
||||
global_repo->defineFile("dirA/fileAC");
|
||||
global_repo->defineFile("dirA/subdirA/fileAAA");
|
||||
global_repo->defineFile("dirA/subdirA/fileAAB");
|
||||
global_repo->defineFile("dirA/subdirA/zeroByteFile");
|
||||
|
||||
global_repo->defineFile("dirB/subdirA/fileBAA");
|
||||
global_repo->defineFile("dirB/subdirA/fileBAB");
|
||||
global_repo->defineFile("dirB/subdirA/fileBAC");
|
||||
@@ -800,6 +969,10 @@ int main(int argc, char* argv[])
|
||||
cl.clearAllConnections();
|
||||
|
||||
testCopyInstalledChildren(&cl);
|
||||
testRetryAfterSocketFailure(&cl);
|
||||
testPersistentSocketFailure(&cl);
|
||||
|
||||
testHashOnEmptyFile();
|
||||
|
||||
std::cout << "all tests passed ok" << std::endl;
|
||||
return 0;
|
||||
|
||||
@@ -32,7 +32,7 @@ void testTarGz()
|
||||
uint8_t* buf = (uint8_t*) alloca(8192);
|
||||
size_t bufSize = f.read((char*) buf, 8192);
|
||||
|
||||
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::TarData);
|
||||
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::GZData);
|
||||
|
||||
f.close();
|
||||
}
|
||||
@@ -174,6 +174,34 @@ void testPAXAttributes()
|
||||
|
||||
}
|
||||
|
||||
void testExtractXZ()
|
||||
{
|
||||
SGPath p = SGPath(SRC_DIR);
|
||||
p.append("test.tar.xz");
|
||||
|
||||
SGBinaryFile f(p);
|
||||
f.open(SG_IO_IN);
|
||||
|
||||
SGPath extractDir = simgear::Dir::current().path() / "test_extract_xz";
|
||||
simgear::Dir pd(extractDir);
|
||||
pd.removeChildren();
|
||||
|
||||
ArchiveExtractor ex(extractDir);
|
||||
|
||||
uint8_t* buf = (uint8_t*)alloca(128);
|
||||
while (!f.eof()) {
|
||||
size_t bufSize = f.read((char*)buf, 128);
|
||||
ex.extractBytes(buf, bufSize);
|
||||
}
|
||||
|
||||
ex.flush();
|
||||
SG_VERIFY(ex.isAtEndOfArchive());
|
||||
SG_VERIFY(ex.hasError() == false);
|
||||
|
||||
SG_VERIFY((extractDir / "testDir/hello.c").exists());
|
||||
SG_VERIFY((extractDir / "testDir/foo.txt").exists());
|
||||
}
|
||||
|
||||
int main(int ac, char ** av)
|
||||
{
|
||||
testTarGz();
|
||||
@@ -181,7 +209,8 @@ int main(int ac, char ** av)
|
||||
testFilterTar();
|
||||
testExtractStreamed();
|
||||
testExtractZip();
|
||||
|
||||
testExtractXZ();
|
||||
|
||||
// disabled to avoiding checking in large PAX archive
|
||||
// testPAXAttributes();
|
||||
|
||||
|
||||
@@ -31,87 +31,18 @@
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/io/sg_file.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/package/unzip.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include "ArchiveExtractor_private.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class ArchiveExtractorPrivate
|
||||
{
|
||||
public:
|
||||
ArchiveExtractorPrivate(ArchiveExtractor* o) :
|
||||
outer(o)
|
||||
{
|
||||
assert(outer);
|
||||
}
|
||||
|
||||
virtual ~ArchiveExtractorPrivate() = default;
|
||||
|
||||
typedef enum {
|
||||
INVALID = 0,
|
||||
READING_HEADER,
|
||||
READING_FILE,
|
||||
READING_PADDING,
|
||||
READING_PAX_GLOBAL_ATTRIBUTES,
|
||||
READING_PAX_FILE_ATTRIBUTES,
|
||||
PRE_END_OF_ARCHVE,
|
||||
END_OF_ARCHIVE,
|
||||
ERROR_STATE, ///< states above this are error conditions
|
||||
BAD_ARCHIVE,
|
||||
BAD_DATA,
|
||||
FILTER_STOPPED
|
||||
} State;
|
||||
|
||||
State state = INVALID;
|
||||
ArchiveExtractor* outer = nullptr;
|
||||
|
||||
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
|
||||
|
||||
virtual void flush() = 0;
|
||||
|
||||
SGPath extractRootPath()
|
||||
{
|
||||
return outer->_rootPath;
|
||||
}
|
||||
|
||||
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
|
||||
{
|
||||
return outer->filterPath(pathToExtract);
|
||||
}
|
||||
|
||||
|
||||
bool isSafePath(const std::string& p) const
|
||||
{
|
||||
if (p.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject absolute paths
|
||||
if (p.at(0) == '/') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reject paths containing '..'
|
||||
size_t doubleDot = p.find("..");
|
||||
if (doubleDot != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// on POSIX could use realpath to sanity check
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
|
||||
const int ZLIB_DECODE_GZIP_HEADER = 16;
|
||||
|
||||
/* tar Header Block, from POSIX 1003.1-1990. */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -151,320 +82,410 @@ typedef struct
|
||||
#define FIFOTYPE '6' /* FIFO special */
|
||||
#define CONTTYPE '7' /* reserved */
|
||||
|
||||
const char PAX_GLOBAL_HEADER = 'g';
|
||||
const char PAX_FILE_ATTRIBUTES = 'x';
|
||||
const char PAX_GLOBAL_HEADER = 'g';
|
||||
const char PAX_FILE_ATTRIBUTES = 'x';
|
||||
|
||||
|
||||
class TarExtractorPrivate : public ArchiveExtractorPrivate
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
|
||||
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
|
||||
const int ZLIB_DECODE_GZIP_HEADER = 16;
|
||||
|
||||
/* tar Header Block, from POSIX 1003.1-1990. */
|
||||
|
||||
|
||||
class TarExtractorPrivate : public ArchiveExtractorPrivate
|
||||
{
|
||||
public:
|
||||
union {
|
||||
UstarHeaderBlock header;
|
||||
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
|
||||
};
|
||||
|
||||
size_t bytesRemaining;
|
||||
std::unique_ptr<SGFile> currentFile;
|
||||
size_t currentFileSize;
|
||||
|
||||
uint8_t* headerPtr;
|
||||
bool skipCurrentEntry = false;
|
||||
std::string paxAttributes;
|
||||
std::string paxPathName;
|
||||
|
||||
TarExtractorPrivate(ArchiveExtractor* o) : ArchiveExtractorPrivate(o)
|
||||
{
|
||||
setState(TarExtractorPrivate::READING_HEADER);
|
||||
}
|
||||
|
||||
~TarExtractorPrivate() = default;
|
||||
|
||||
void readPaddingIfRequired()
|
||||
{
|
||||
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
|
||||
if (pad) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
|
||||
setState(READING_PADDING);
|
||||
} else {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEndOfState()
|
||||
{
|
||||
if (bytesRemaining > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->close();
|
||||
currentFile.reset();
|
||||
}
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_HEADER) {
|
||||
processHeader();
|
||||
} else if (state == PRE_END_OF_ARCHVE) {
|
||||
if (headerIsAllZeros()) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
} else {
|
||||
// what does the spec say here?
|
||||
}
|
||||
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
|
||||
parsePAXAttributes(true);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
|
||||
parsePAXAttributes(false);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PADDING) {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void setState(State newState)
|
||||
{
|
||||
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
|
||||
headerPtr = headerBytes;
|
||||
}
|
||||
|
||||
state = newState;
|
||||
}
|
||||
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
// uncompressed, just pass through directly
|
||||
processBytes((const char*)bytes, count);
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
// no-op for tar files, we process everything greedily
|
||||
}
|
||||
|
||||
void processHeader()
|
||||
{
|
||||
if (headerIsAllZeros()) {
|
||||
if (state == PRE_END_OF_ARCHVE) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
} else {
|
||||
setState(PRE_END_OF_ARCHVE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
|
||||
state = BAD_ARCHIVE;
|
||||
return;
|
||||
}
|
||||
|
||||
skipCurrentEntry = false;
|
||||
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
|
||||
if (!paxPathName.empty()) {
|
||||
tarPath = paxPathName;
|
||||
paxPathName.clear(); // clear for next file
|
||||
}
|
||||
|
||||
if (!isSafePath(tarPath)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
auto result = filterPath(tarPath);
|
||||
if (result == ArchiveExtractor::Stop) {
|
||||
setState(FILTER_STOPPED);
|
||||
return;
|
||||
} else if (result == ArchiveExtractor::Skipped) {
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
SGPath p = extractRootPath() / tarPath;
|
||||
if (header.typeflag == DIRTYPE) {
|
||||
if (!skipCurrentEntry) {
|
||||
Dir dir(p);
|
||||
dir.create(0755);
|
||||
}
|
||||
setState(READING_HEADER);
|
||||
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
if (!skipCurrentEntry) {
|
||||
currentFile.reset(new SGBinaryFile(p));
|
||||
currentFile->open(SG_IO_OUT);
|
||||
}
|
||||
setState(READING_FILE);
|
||||
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
|
||||
setState(READING_PAX_GLOBAL_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
|
||||
setState(READING_PAX_FILE_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
setState(READING_HEADER);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
|
||||
state = BAD_ARCHIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void processBytes(const char* bytes, size_t count)
|
||||
{
|
||||
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t curBytes = std::min(bytesRemaining, count);
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->write(bytes, curBytes);
|
||||
}
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
|
||||
memcpy(headerPtr, bytes, curBytes);
|
||||
bytesRemaining -= curBytes;
|
||||
headerPtr += curBytes;
|
||||
} else if (state == READING_PADDING) {
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
|
||||
bytesRemaining -= curBytes;
|
||||
paxAttributes.append(bytes, curBytes);
|
||||
}
|
||||
|
||||
checkEndOfState();
|
||||
if (count > curBytes) {
|
||||
// recurse with unprocessed bytes
|
||||
processBytes(bytes + curBytes, count - curBytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool headerIsAllZeros() const
|
||||
{
|
||||
char* headerAsChar = (char*)&header;
|
||||
for (size_t i = 0; i < offsetof(UstarHeaderBlock, magic); ++i) {
|
||||
if (*headerAsChar++ != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
|
||||
void parsePAXAttributes(bool areGlobal)
|
||||
{
|
||||
auto lineStart = 0;
|
||||
for (;;) {
|
||||
auto firstSpace = paxAttributes.find(' ', lineStart);
|
||||
auto firstEq = paxAttributes.find('=', lineStart);
|
||||
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
|
||||
uint32_t dataBytes = lengthBytes - (firstEq + 1);
|
||||
std::string name = paxAttributes.substr(firstSpace + 1, firstEq - (firstSpace + 1));
|
||||
|
||||
// dataBytes - 1 here to trim off the trailing newline
|
||||
std::string data = paxAttributes.substr(firstEq + 1, dataBytes - 1);
|
||||
|
||||
processPAXAttribute(areGlobal, name, data);
|
||||
|
||||
lineStart += lengthBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
|
||||
{
|
||||
if (!isGlobalAttr && (attrName == "path")) {
|
||||
// data is UTF-8 encoded path name
|
||||
paxPathName = data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class GZTarExtractor : public TarExtractorPrivate
|
||||
{
|
||||
public:
|
||||
|
||||
union {
|
||||
UstarHeaderBlock header;
|
||||
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
|
||||
};
|
||||
|
||||
size_t bytesRemaining;
|
||||
std::unique_ptr<SGFile> currentFile;
|
||||
size_t currentFileSize;
|
||||
z_stream zlibStream;
|
||||
uint8_t* zlibOutput;
|
||||
bool haveInitedZLib = false;
|
||||
bool uncompressedData = false; // set if reading a plain .tar (not tar.gz)
|
||||
uint8_t* headerPtr;
|
||||
bool skipCurrentEntry = false;
|
||||
std::string paxAttributes;
|
||||
std::string paxPathName;
|
||||
|
||||
TarExtractorPrivate(ArchiveExtractor* o) :
|
||||
ArchiveExtractorPrivate(o)
|
||||
GZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
|
||||
{
|
||||
memset(&zlibStream, 0, sizeof(z_stream));
|
||||
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
zlibStream.zalloc = Z_NULL;
|
||||
zlibStream.zfree = Z_NULL;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
zlibStream.next_out = zlibOutput;
|
||||
memset(&zlibStream, 0, sizeof(z_stream));
|
||||
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
zlibStream.zalloc = Z_NULL;
|
||||
zlibStream.zfree = Z_NULL;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
zlibStream.next_out = zlibOutput;
|
||||
}
|
||||
|
||||
~TarExtractorPrivate()
|
||||
~GZTarExtractor()
|
||||
{
|
||||
free(zlibOutput);
|
||||
}
|
||||
|
||||
void readPaddingIfRequired()
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
|
||||
if (pad) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
|
||||
setState(READING_PADDING);
|
||||
} else {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEndOfState()
|
||||
{
|
||||
if (bytesRemaining > 0) {
|
||||
return;
|
||||
}
|
||||
zlibStream.next_in = (uint8_t*)bytes;
|
||||
zlibStream.avail_in = count;
|
||||
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->close();
|
||||
currentFile.reset();
|
||||
}
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_HEADER) {
|
||||
processHeader();
|
||||
} else if (state == PRE_END_OF_ARCHVE) {
|
||||
if (headerIsAllZeros()) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
if (!haveInitedZLib) {
|
||||
// now we have data, see if we're dealing with GZ-compressed data or not
|
||||
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
|
||||
// GZIP identification bytes
|
||||
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// what does the spec say here?
|
||||
// set error state
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
|
||||
parsePAXAttributes(true);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
|
||||
parsePAXAttributes(false);
|
||||
readPaddingIfRequired();
|
||||
} else if (state == READING_PADDING) {
|
||||
setState(READING_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
void setState(State newState)
|
||||
{
|
||||
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
|
||||
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
|
||||
headerPtr = headerBytes;
|
||||
}
|
||||
haveInitedZLib = true;
|
||||
setState(TarExtractorPrivate::READING_HEADER);
|
||||
} // of init on first-bytes case
|
||||
|
||||
state = newState;
|
||||
}
|
||||
size_t writtenSize;
|
||||
// loop, running zlib() inflate and sending output bytes to
|
||||
// our request body handler. Keep calling inflate until no bytes are
|
||||
// written, and ZLIB has consumed all available input
|
||||
do {
|
||||
zlibStream.next_out = zlibOutput;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
int result = inflate(&zlibStream, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
zlibStream.next_in = (uint8_t*) bytes;
|
||||
zlibStream.avail_in = count;
|
||||
|
||||
if (!haveInitedZLib) {
|
||||
// now we have data, see if we're dealing with GZ-compressed data or not
|
||||
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
|
||||
// GZIP identification bytes
|
||||
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
|
||||
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
UstarHeaderBlock* header = (UstarHeaderBlock*)bytes;
|
||||
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "didn't find tar magic in header");
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
|
||||
uncompressedData = true;
|
||||
}
|
||||
|
||||
haveInitedZLib = true;
|
||||
setState(TarExtractorPrivate::READING_HEADER);
|
||||
} // of init on first-bytes case
|
||||
|
||||
if (uncompressedData) {
|
||||
processBytes((const char*) bytes, count);
|
||||
} else {
|
||||
size_t writtenSize;
|
||||
// loop, running zlib() inflate and sending output bytes to
|
||||
// our request body handler. Keep calling inflate until no bytes are
|
||||
// written, and ZLIB has consumed all available input
|
||||
do {
|
||||
zlibStream.next_out = zlibOutput;
|
||||
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
int result = inflate(&zlibStream, Z_NO_FLUSH);
|
||||
if (result == Z_OK || result == Z_STREAM_END) {
|
||||
// nothing to do
|
||||
|
||||
}
|
||||
else if (result == Z_BUF_ERROR) {
|
||||
// transient error, fall through
|
||||
}
|
||||
else {
|
||||
// _error = result;
|
||||
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
|
||||
if (writtenSize > 0) {
|
||||
processBytes((const char*) zlibOutput, writtenSize);
|
||||
}
|
||||
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
|
||||
} // of Zlib-compressed data
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
// no-op for tar files, we process everything greedily
|
||||
}
|
||||
|
||||
void processHeader()
|
||||
{
|
||||
if (headerIsAllZeros()) {
|
||||
if (state == PRE_END_OF_ARCHVE) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
} else if (result == Z_BUF_ERROR) {
|
||||
// transient error, fall through
|
||||
} else {
|
||||
setState(PRE_END_OF_ARCHVE);
|
||||
// _error = result;
|
||||
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
|
||||
state = TarExtractorPrivate::BAD_DATA;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
|
||||
state = BAD_ARCHIVE;
|
||||
return;
|
||||
}
|
||||
|
||||
skipCurrentEntry = false;
|
||||
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
|
||||
if (!paxPathName.empty()) {
|
||||
tarPath = paxPathName;
|
||||
paxPathName.clear(); // clear for next file
|
||||
}
|
||||
|
||||
if (!isSafePath(tarPath)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
auto result = filterPath(tarPath);
|
||||
if (result == ArchiveExtractor::Stop) {
|
||||
setState(FILTER_STOPPED);
|
||||
return;
|
||||
} else if (result == ArchiveExtractor::Skipped) {
|
||||
skipCurrentEntry = true;
|
||||
}
|
||||
|
||||
SGPath p = extractRootPath() / tarPath;
|
||||
if (header.typeflag == DIRTYPE) {
|
||||
if (!skipCurrentEntry) {
|
||||
Dir dir(p);
|
||||
dir.create(0755);
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
|
||||
if (writtenSize > 0) {
|
||||
processBytes((const char*)zlibOutput, writtenSize);
|
||||
}
|
||||
setState(READING_HEADER);
|
||||
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
if (!skipCurrentEntry) {
|
||||
currentFile.reset(new SGBinaryFile(p));
|
||||
currentFile->open(SG_IO_OUT);
|
||||
}
|
||||
setState(READING_FILE);
|
||||
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
|
||||
setState(READING_PAX_GLOBAL_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
|
||||
setState(READING_PAX_FILE_ATTRIBUTES);
|
||||
currentFileSize = ::strtol(header.size, NULL, 8);
|
||||
bytesRemaining = currentFileSize;
|
||||
paxAttributes.clear();
|
||||
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
|
||||
skipCurrentEntry = true;
|
||||
setState(READING_HEADER);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
|
||||
state = BAD_ARCHIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void processBytes(const char* bytes, size_t count)
|
||||
{
|
||||
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t curBytes = std::min(bytesRemaining, count);
|
||||
if (state == READING_FILE) {
|
||||
if (currentFile) {
|
||||
currentFile->write(bytes, curBytes);
|
||||
}
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
|
||||
memcpy(headerPtr, bytes, curBytes);
|
||||
bytesRemaining -= curBytes;
|
||||
headerPtr += curBytes;
|
||||
} else if (state == READING_PADDING) {
|
||||
bytesRemaining -= curBytes;
|
||||
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
|
||||
bytesRemaining -= curBytes;
|
||||
paxAttributes.append(bytes, curBytes);
|
||||
}
|
||||
|
||||
checkEndOfState();
|
||||
if (count > curBytes) {
|
||||
// recurse with unprocessed bytes
|
||||
processBytes(bytes + curBytes, count - curBytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool headerIsAllZeros() const
|
||||
{
|
||||
char* headerAsChar = (char*) &header;
|
||||
for (size_t i=0; i < offsetof(UstarHeaderBlock, magic); ++i) {
|
||||
if (*headerAsChar++ != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
|
||||
void parsePAXAttributes(bool areGlobal)
|
||||
{
|
||||
auto lineStart = 0;
|
||||
for (;;) {
|
||||
auto firstSpace = paxAttributes.find(' ', lineStart);
|
||||
auto firstEq = paxAttributes.find('=', lineStart);
|
||||
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
|
||||
if (result == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
|
||||
uint32_t dataBytes = lengthBytes - (firstEq + 1);
|
||||
std::string name = paxAttributes.substr(firstSpace+1, firstEq - (firstSpace + 1));
|
||||
|
||||
// dataBytes - 1 here to trim off the trailing newline
|
||||
std::string data = paxAttributes.substr(firstEq+1, dataBytes - 1);
|
||||
|
||||
processPAXAttribute(areGlobal, name, data);
|
||||
|
||||
lineStart += lengthBytes;
|
||||
}
|
||||
}
|
||||
|
||||
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
|
||||
{
|
||||
if (!isGlobalAttr && (attrName == "path")) {
|
||||
// data is UTF-8 encoded path name
|
||||
paxPathName = data;
|
||||
}
|
||||
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
|
||||
}
|
||||
|
||||
private:
|
||||
z_stream zlibStream;
|
||||
uint8_t* zlibOutput;
|
||||
bool haveInitedZLib = false;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if 1 || defined(HAVE_XZ)
|
||||
|
||||
#include <lzma.h>
|
||||
|
||||
class XZTarExtractor : public TarExtractorPrivate
|
||||
{
|
||||
public:
|
||||
XZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
|
||||
{
|
||||
_xzStream = LZMA_STREAM_INIT;
|
||||
_outputBuffer = (uint8_t*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
|
||||
|
||||
|
||||
auto ret = lzma_stream_decoder(&_xzStream, UINT64_MAX, LZMA_TELL_ANY_CHECK);
|
||||
if (ret != LZMA_OK) {
|
||||
setState(BAD_ARCHIVE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~XZTarExtractor()
|
||||
{
|
||||
free(_outputBuffer);
|
||||
}
|
||||
|
||||
void extractBytes(const uint8_t* bytes, size_t count) override
|
||||
{
|
||||
lzma_action action = LZMA_RUN;
|
||||
_xzStream.next_in = bytes;
|
||||
_xzStream.avail_in = count;
|
||||
|
||||
size_t writtenSize;
|
||||
do {
|
||||
_xzStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
|
||||
_xzStream.next_out = _outputBuffer;
|
||||
|
||||
const auto ret = lzma_code(&_xzStream, action);
|
||||
|
||||
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _xzStream.avail_out;
|
||||
if (writtenSize > 0) {
|
||||
processBytes((const char*)_outputBuffer, writtenSize);
|
||||
}
|
||||
|
||||
if (ret == LZMA_GET_CHECK) {
|
||||
//
|
||||
} else if (ret == LZMA_STREAM_END) {
|
||||
setState(END_OF_ARCHIVE);
|
||||
break;
|
||||
} else if (ret != LZMA_OK) {
|
||||
setState(BAD_ARCHIVE);
|
||||
break;
|
||||
}
|
||||
} while ((_xzStream.avail_in > 0) || (writtenSize > 0));
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
const auto ret = lzma_code(&_xzStream, LZMA_FINISH);
|
||||
if (ret != LZMA_STREAM_END) {
|
||||
setState(BAD_ARCHIVE);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
lzma_stream _xzStream;
|
||||
uint8_t* _outputBuffer = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
extern "C" {
|
||||
void fill_memory_filefunc(zlib_filefunc_def*);
|
||||
}
|
||||
@@ -509,32 +530,34 @@ public:
|
||||
const size_t BUFFER_SIZE = 1024 * 1024;
|
||||
void* buf = malloc(BUFFER_SIZE);
|
||||
|
||||
try {
|
||||
int result = unzGoToFirstFile(zip);
|
||||
if (result != UNZ_OK) {
|
||||
throw sg_exception("failed to go to first file in archive");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
extractCurrentFile(zip, (char*)buf, BUFFER_SIZE);
|
||||
if (state == FILTER_STOPPED) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = unzGoToNextFile(zip);
|
||||
if (result == UNZ_END_OF_LIST_OF_FILE) {
|
||||
break;
|
||||
}
|
||||
else if (result != UNZ_OK) {
|
||||
throw sg_io_exception("failed to go to next file in the archive");
|
||||
}
|
||||
}
|
||||
state = END_OF_ARCHIVE;
|
||||
}
|
||||
catch (sg_exception&) {
|
||||
int result = unzGoToFirstFile(zip);
|
||||
if (result != UNZ_OK) {
|
||||
SG_LOG(SG_IO, SG_ALERT, outer->rootPath() << "failed to go to first file in archive:" << result);
|
||||
state = BAD_ARCHIVE;
|
||||
free(buf);
|
||||
unzClose(zip);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
extractCurrentFile(zip, (char*)buf, BUFFER_SIZE);
|
||||
if (state == FILTER_STOPPED) {
|
||||
state = END_OF_ARCHIVE;
|
||||
break;
|
||||
}
|
||||
|
||||
result = unzGoToNextFile(zip);
|
||||
if (result == UNZ_END_OF_LIST_OF_FILE) {
|
||||
state = END_OF_ARCHIVE;
|
||||
break;
|
||||
} else if (result != UNZ_OK) {
|
||||
SG_LOG(SG_IO, SG_ALERT, outer->rootPath() << "failed to go to next file in archive:" << result);
|
||||
state = BAD_ARCHIVE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(buf);
|
||||
unzClose(zip);
|
||||
}
|
||||
@@ -592,7 +615,7 @@ public:
|
||||
|
||||
outFile.open(path, std::ios::binary | std::ios::trunc | std::ios::out);
|
||||
if (outFile.fail()) {
|
||||
throw sg_io_exception("failed to open output file for writing", path);
|
||||
throw sg_io_exception("failed to open output file for writing:" + strutils::error_string(errno), path);
|
||||
}
|
||||
|
||||
while (!eof) {
|
||||
@@ -633,17 +656,19 @@ void ArchiveExtractor::extractBytes(const uint8_t* bytes, size_t count)
|
||||
|
||||
if (r == TarData) {
|
||||
d.reset(new TarExtractorPrivate(this));
|
||||
}
|
||||
else if (r == ZipData) {
|
||||
d.reset(new ZipExtractorPrivate(this));
|
||||
}
|
||||
else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
|
||||
} else if (r == GZData) {
|
||||
d.reset(new GZTarExtractor(this));
|
||||
} else if (r == XZData) {
|
||||
d.reset(new XZTarExtractor(this));
|
||||
} else if (r == ZipData) {
|
||||
d.reset(new ZipExtractorPrivate(this));
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
|
||||
_invalidDataType = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if hit here, we created the extractor. Feed the prefbuffer
|
||||
// if hit here, we created the extractor. Feed the prefbuffer
|
||||
// bytes through it
|
||||
d->extractBytes((uint8_t*) _prebuffer.data(), _prebuffer.size());
|
||||
_prebuffer.clear();
|
||||
@@ -695,9 +720,18 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::determineType(const uint8_t*
|
||||
return ZipData;
|
||||
}
|
||||
|
||||
auto r = isTarData(bytes, count);
|
||||
if ((r == TarData) || (r == InsufficientData))
|
||||
return r;
|
||||
if (count < 6) {
|
||||
return InsufficientData;
|
||||
}
|
||||
|
||||
const uint8_t XZ_HEADER[6] = {0xFD, '7', 'z', 'X', 'Z', 0x00};
|
||||
if (memcmp(bytes, XZ_HEADER, 6) == 0) {
|
||||
return XZData;
|
||||
}
|
||||
|
||||
auto r = isTarData(bytes, count);
|
||||
if ((r == TarData) || (r == InsufficientData) || (r == GZData))
|
||||
return r;
|
||||
|
||||
return Invalid;
|
||||
}
|
||||
@@ -710,6 +744,8 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
}
|
||||
|
||||
UstarHeaderBlock* header = 0;
|
||||
DetermineResult result = InsufficientData;
|
||||
|
||||
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
|
||||
// GZIP identification bytes
|
||||
z_stream z;
|
||||
@@ -727,11 +763,11 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
int result = inflate(&z, Z_SYNC_FLUSH);
|
||||
if ((result == Z_OK) || (result == Z_STREAM_END)) {
|
||||
int zResult = inflate(&z, Z_SYNC_FLUSH);
|
||||
if ((zResult == Z_OK) || (zResult == Z_STREAM_END)) {
|
||||
// all good
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << result);
|
||||
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << zResult);
|
||||
inflateEnd(&z);
|
||||
return Invalid; // not tar data
|
||||
}
|
||||
@@ -744,6 +780,7 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
|
||||
header = reinterpret_cast<UstarHeaderBlock*>(zlibOutput);
|
||||
inflateEnd(&z);
|
||||
result = GZData;
|
||||
} else {
|
||||
// uncompressed tar
|
||||
if (count < TAR_HEADER_BLOCK_SIZE) {
|
||||
@@ -751,13 +788,14 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
|
||||
}
|
||||
|
||||
header = (UstarHeaderBlock*) bytes;
|
||||
result = TarData;
|
||||
}
|
||||
|
||||
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
return TarData;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ArchiveExtractor::extractLocalFile(const SGPath& archiveFile)
|
||||
|
||||
@@ -36,15 +36,16 @@ public:
|
||||
ArchiveExtractor(const SGPath& rootPath);
|
||||
virtual ~ArchiveExtractor();
|
||||
|
||||
enum DetermineResult
|
||||
{
|
||||
Invalid,
|
||||
InsufficientData,
|
||||
TarData,
|
||||
ZipData
|
||||
};
|
||||
enum DetermineResult {
|
||||
Invalid,
|
||||
InsufficientData,
|
||||
TarData,
|
||||
ZipData,
|
||||
GZData, // Gzipped-tar
|
||||
XZData // XZ (aka LZMA) tar
|
||||
};
|
||||
|
||||
static DetermineResult determineType(const uint8_t* bytes, size_t count);
|
||||
static DetermineResult determineType(const uint8_t* bytes, size_t count);
|
||||
|
||||
/**
|
||||
* @brief API to extract a local zip or tar.gz
|
||||
@@ -70,6 +71,11 @@ public:
|
||||
Stop
|
||||
};
|
||||
|
||||
SGPath rootPath() const
|
||||
{
|
||||
return _rootPath;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,13 @@ public:
|
||||
/// Default constructor, initializes the instance to lat = lon = elev = 0
|
||||
SGGeod(void);
|
||||
|
||||
/**
|
||||
return an SGGeod for which isValid() returns false.
|
||||
This is necessaerby becuase for historical reasons, ther defaulrt constructor above initialsies to zero,zero,zero
|
||||
which *is*
|
||||
*/
|
||||
static SGGeod invalid();
|
||||
|
||||
/// Factory from angular values in radians and elevation is 0
|
||||
static SGGeod fromRad(double lon, double lat);
|
||||
/// Factory from angular values in degrees and elevation is 0
|
||||
|
||||
@@ -537,6 +537,13 @@ SGGeodesy::advanceRadM(const SGGeoc& geoc, double course, double distance,
|
||||
}
|
||||
}
|
||||
|
||||
SGGeoc SGGeodesy::advanceDegM(const SGGeoc &geoc, double course,
|
||||
double distance) {
|
||||
SGGeoc result;
|
||||
advanceRadM(geoc, course * SG_DEGREES_TO_RADIANS, distance, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
double
|
||||
SGGeodesy::courseRad(const SGGeoc& from, const SGGeoc& to)
|
||||
{
|
||||
@@ -656,3 +663,8 @@ SGGeodesy::radialIntersection(const SGGeod& a, double aRadial,
|
||||
result = SGGeod::fromGeoc(r);
|
||||
return true;
|
||||
}
|
||||
|
||||
SGGeod SGGeod::invalid()
|
||||
{
|
||||
return SGGeod::fromDeg(-999.9, -999.0);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ public:
|
||||
// Geocentric course/distance computation
|
||||
static void advanceRadM(const SGGeoc& geoc, double course, double distance,
|
||||
SGGeoc& result);
|
||||
|
||||
static SGGeoc advanceDegM(const SGGeoc &geoc, double course, double distance);
|
||||
|
||||
static double courseRad(const SGGeoc& from, const SGGeoc& to);
|
||||
static double distanceRad(const SGGeoc& from, const SGGeoc& to);
|
||||
static double distanceM(const SGGeoc& from, const SGGeoc& to);
|
||||
|
||||
@@ -49,6 +49,11 @@ ResourceManager* ResourceManager::instance()
|
||||
return static_manager;
|
||||
}
|
||||
|
||||
bool ResourceManager::haveInstance()
|
||||
{
|
||||
return static_manager != nullptr;
|
||||
}
|
||||
|
||||
ResourceManager::~ResourceManager()
|
||||
{
|
||||
assert(this == static_manager);
|
||||
@@ -56,6 +61,15 @@ ResourceManager::~ResourceManager()
|
||||
std::for_each(_providers.begin(), _providers.end(),
|
||||
[](ResourceProvider* p) { delete p; });
|
||||
}
|
||||
|
||||
void ResourceManager::reset()
|
||||
{
|
||||
if (static_manager) {
|
||||
delete static_manager;
|
||||
static_manager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* trivial provider using a fixed base path
|
||||
*/
|
||||
@@ -107,6 +121,8 @@ void ResourceManager::removeProvider(ResourceProvider* aProvider)
|
||||
SG_LOG(SG_GENERAL, SG_DEV_ALERT, "unknown provider doing remove");
|
||||
return;
|
||||
}
|
||||
|
||||
_providers.erase(it);
|
||||
}
|
||||
|
||||
SGPath ResourceManager::findPath(const std::string& aResource, SGPath aContext)
|
||||
|
||||
@@ -45,8 +45,12 @@ public:
|
||||
PRIORITY_HIGH = 1000
|
||||
} Priority;
|
||||
|
||||
static ResourceManager* instance();
|
||||
|
||||
static ResourceManager* instance();
|
||||
|
||||
static bool haveInstance();
|
||||
|
||||
static void reset();
|
||||
|
||||
/**
|
||||
* add a simple fixed resource location, to resolve against
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#if defined(SG_WINDOWS)
|
||||
# include <direct.h>
|
||||
# include <sys/utime.h>
|
||||
# include <Shlwapi.h>
|
||||
#endif
|
||||
|
||||
#include "sg_path.hxx"
|
||||
@@ -194,7 +195,8 @@ SGPath::SGPath(PermissionChecker validator)
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(true)
|
||||
_cacheEnabled(true),
|
||||
_existsCached(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -205,7 +207,8 @@ SGPath::SGPath( const std::string& p, PermissionChecker validator )
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(true)
|
||||
_cacheEnabled(true),
|
||||
_existsCached(false)
|
||||
{
|
||||
fix();
|
||||
}
|
||||
@@ -230,7 +233,8 @@ SGPath::SGPath( const SGPath& p,
|
||||
_permission_checker(validator),
|
||||
_cached(false),
|
||||
_rwCached(false),
|
||||
_cacheEnabled(p._cacheEnabled)
|
||||
_cacheEnabled(p._cacheEnabled),
|
||||
_existsCached(false)
|
||||
{
|
||||
append(r);
|
||||
fix();
|
||||
@@ -510,6 +514,19 @@ void SGPath::checkAccess() const
|
||||
|
||||
bool SGPath::exists() const
|
||||
{
|
||||
#if defined(SG_WINDOWS)
|
||||
// optimisation: _wstat is slow, eg for TerraSync
|
||||
if (!_cached && !_existsCached) {
|
||||
std::wstring w(wstr());
|
||||
if ((path.length() > 1) && (path.back() == '/')) {
|
||||
w.pop_back();
|
||||
}
|
||||
|
||||
_existsCached = true;
|
||||
_exists = PathFileExistsW(w.c_str());
|
||||
return _exists;
|
||||
}
|
||||
#endif
|
||||
validate();
|
||||
return _exists;
|
||||
}
|
||||
|
||||
@@ -356,6 +356,7 @@ private:
|
||||
mutable bool _exists : 1;
|
||||
mutable bool _isDir : 1;
|
||||
mutable bool _isFile : 1;
|
||||
mutable bool _existsCached : 1; ///< only used on Windows
|
||||
mutable time_t _modTime;
|
||||
mutable size_t _size;
|
||||
};
|
||||
|
||||
@@ -267,6 +267,38 @@ namespace simgear {
|
||||
return do_strip( s, BOTHSTRIP );
|
||||
}
|
||||
|
||||
string makeStringSafeForPropertyName(const std::string& str)
|
||||
{
|
||||
// This function replaces all characters in 'str' that are not
|
||||
// alphanumeric or '-'.
|
||||
// TODO: make the function multibyte safe.
|
||||
string res = str;
|
||||
|
||||
int index = 0;
|
||||
for (char& c : res) {
|
||||
if (!std::isalpha(c) && !std::isdigit(c) && c != '-') {
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '_':
|
||||
case '.':
|
||||
case '/':
|
||||
case '\\':
|
||||
res[index] = '-';
|
||||
break;
|
||||
default:
|
||||
res[index] = '_';
|
||||
SG_LOG(SG_GENERAL, SG_WARN, "makeStringSafeForPropertyName: Modified '" << str << "' to '" << res << "'");
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
stripTrailingNewlines_inplace(string& s)
|
||||
{
|
||||
|
||||
@@ -75,7 +75,9 @@ namespace simgear {
|
||||
std::string rstrip( const std::string& s );
|
||||
std::string strip( const std::string& s );
|
||||
|
||||
/**
|
||||
std::string makeStringSafeForPropertyName(const std::string& str);
|
||||
|
||||
/**
|
||||
* Return a new string with any trailing \\r and \\n characters removed.
|
||||
* Typically useful to clean a CR-terminated line obtained from
|
||||
* std::getline() which, upon reading CRLF (\\r\\n), discards the Line
|
||||
|
||||
@@ -737,6 +737,11 @@ void testDecodeHex()
|
||||
SG_VERIFY(decoded == data1);
|
||||
}
|
||||
|
||||
void test_makeStringSafeForPropertyName()
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::makeStringSafeForPropertyName(" ABC/01234\t:\\\"_*$"), "-ABC-01234-_-_-__");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
test_strip();
|
||||
@@ -761,6 +766,7 @@ int main(int argc, char* argv[])
|
||||
test_formatGeod();
|
||||
test_iequals();
|
||||
testDecodeHex();
|
||||
|
||||
test_makeStringSafeForPropertyName();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ void naRuntimeError(naContext c, const char* fmt, ...)
|
||||
|
||||
void naRethrowError(naContext subc)
|
||||
{
|
||||
strncpy(subc->callParent->error, subc->error, sizeof(subc->error));
|
||||
strncpy(subc->callParent->error, subc->error, sizeof(subc->callParent->error));
|
||||
subc->callParent->dieArg = subc->dieArg;
|
||||
longjmp(subc->callParent->jumpHandle, 1);
|
||||
}
|
||||
@@ -248,7 +248,7 @@ void naFreeContext(naContext c)
|
||||
// than I have right now. So instead I'm clearing the stack tops here, so
|
||||
// a freed context looks the same as a new one returned by initContext.
|
||||
|
||||
c->fTop = c->opTop = c->markTop = 0;
|
||||
c->fTop = c->opTop = c->markTop = c->ntemps = 0;
|
||||
|
||||
c->nextFree = globals->freeContexts;
|
||||
globals->freeContexts = c;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "NasalString.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept> // for std::runtime_error
|
||||
|
||||
namespace nasal
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace nasal
|
||||
|
||||
class NasalMainLoopRecipient : public simgear::Emesary::IReceiver {
|
||||
public:
|
||||
NasalMainLoopRecipient() : receiveCount(0) {
|
||||
NasalMainLoopRecipient() : receiveCount(0), Active(false), CanWait(false) {
|
||||
simgear::Emesary::GlobalTransmitter::instance()->Register(*this);
|
||||
}
|
||||
virtual ~NasalMainLoopRecipient() {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "iolib.h"
|
||||
|
||||
static void ghostDestroy(void* g);
|
||||
naGhostType naIOGhostType = { ghostDestroy, "iofile" };
|
||||
naGhostType naIOGhostType = { ghostDestroy, "iofile", NULL, NULL };
|
||||
|
||||
static struct naIOGhost* ioghost(naRef r)
|
||||
{
|
||||
|
||||
@@ -472,7 +472,7 @@ static naRef f_sprintf(naContext c, naRef me, int argc, naRef* args)
|
||||
} else {
|
||||
arg = naNumValue(arg);
|
||||
if(naIsNil(arg))
|
||||
fout = dosprintf(fstr, "nil");
|
||||
fout = dosprintf("nil");
|
||||
else if(t=='d' || t=='i' || t=='c')
|
||||
fout = dosprintf(fstr, (int)naNumValue(arg).num);
|
||||
else if(t=='o' || t=='u' || t=='x' || t=='X')
|
||||
|
||||
@@ -100,7 +100,14 @@ naRef naNewHash(struct Context* c)
|
||||
|
||||
naRef naNewCode(struct Context* c)
|
||||
{
|
||||
return naNew(c, T_CODE);
|
||||
naRef r = naNew(c, T_CODE);
|
||||
// naNew can return a previously used naCode. naCodeGen will init
|
||||
// all these members but a GC can occur inside naCodeGen, so we see
|
||||
// partially initalized state here. To avoid this, clear out the values
|
||||
// which mark() cares about.
|
||||
PTR(r).code->srcFile = naNil();
|
||||
PTR(r).code->nConstants = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
naRef naNewCCode(struct Context* c, naCFunction fptr)
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include "code.h"
|
||||
|
||||
static void lockDestroy(void* lock) { naFreeLock(lock); }
|
||||
static naGhostType LockType = { lockDestroy };
|
||||
static naGhostType LockType = { lockDestroy, NULL, NULL, NULL };
|
||||
|
||||
static void semDestroy(void* sem) { naFreeSem(sem); }
|
||||
static naGhostType SemType = { semDestroy };
|
||||
static naGhostType SemType = { semDestroy, NULL, NULL, NULL };
|
||||
|
||||
typedef struct {
|
||||
naContext ctx;
|
||||
|
||||
@@ -595,13 +595,17 @@ void Catalog::setUserEnabled(bool b)
|
||||
|
||||
m_userEnabled = b;
|
||||
SGPath disableMarkerFile = installRoot() / "_disabled_";
|
||||
if (m_userEnabled) {
|
||||
sg_ofstream of(disableMarkerFile);
|
||||
of << "1\n"; // touch the file
|
||||
|
||||
if (m_userEnabled == false) {
|
||||
sg_ofstream of(disableMarkerFile, std::ios::trunc | std::ios::out);
|
||||
of << "1" << std::flush; // touch the file
|
||||
of.close();
|
||||
} else {
|
||||
bool ok = disableMarkerFile.remove();
|
||||
if (!ok) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Failed to remove catalog-disable marker file:" << disableMarkerFile);
|
||||
if (disableMarkerFile.exists()) {
|
||||
const bool ok = disableMarkerFile.remove();
|
||||
if (!ok) {
|
||||
SG_LOG(SG_IO, SG_WARN, "Failed to remove catalog-disable marker file:" << disableMarkerFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,16 +643,53 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
|
||||
return;
|
||||
}
|
||||
|
||||
// we have an alternate ID, and it's differnt from our ID, so let's
|
||||
// we have an alternate ID, and it's different from our ID, so let's
|
||||
// define a new catalog
|
||||
if (!altId.empty()) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "Adding new catalog:" << altId << " as version alternate for " << id());
|
||||
// new catalog being added
|
||||
createFromUrl(root(), altUrl);
|
||||
|
||||
// and we can go idle now
|
||||
// don't auto-re-add Catalogs the user has explicilty rmeoved, that would
|
||||
// suck
|
||||
const auto removedByUser = root()->explicitlyRemovedCatalogs();
|
||||
auto it = std::find(removedByUser.begin(), removedByUser.end(), altId);
|
||||
if (it != removedByUser.end()) {
|
||||
changeStatus(Delegate::FAIL_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_WARN,
|
||||
"Adding new catalog:" << altId << " as version alternate for "
|
||||
<< id());
|
||||
// new catalog being added
|
||||
auto newCat = createFromUrl(root(), altUrl);
|
||||
|
||||
bool didRun = false;
|
||||
newCat->m_migratedFrom = this;
|
||||
|
||||
auto migratePackagesCb = [didRun](Catalog *c) mutable {
|
||||
// removing callbacks is awkward, so use this
|
||||
// flag to only run once. (and hence, we need to be mutable)
|
||||
if (didRun)
|
||||
return;
|
||||
|
||||
if (c->status() == Delegate::STATUS_REFRESHED) {
|
||||
didRun = true;
|
||||
|
||||
string_list existing;
|
||||
for (const auto &pack : c->migratedFrom()->installedPackages()) {
|
||||
existing.push_back(pack->id());
|
||||
}
|
||||
|
||||
const int count = c->markPackagesForInstallation(existing);
|
||||
SG_LOG(
|
||||
SG_GENERAL, SG_INFO,
|
||||
"Marked " << count
|
||||
<< " packages from previous catalog for installation");
|
||||
}
|
||||
};
|
||||
|
||||
newCat->addStatusCallback(migratePackagesCb);
|
||||
// and we can go idle now
|
||||
changeStatus(Delegate::FAIL_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "Migrating catalog " << id() << " to new URL:" << altUrl);
|
||||
@@ -657,6 +698,26 @@ void Catalog::processAlternate(SGPropertyNode_ptr alt)
|
||||
root()->makeHTTPRequest(dl);
|
||||
}
|
||||
|
||||
int Catalog::markPackagesForInstallation(const string_list &packageIds) {
|
||||
int result = 0;
|
||||
|
||||
for (const auto &id : packageIds) {
|
||||
auto ourPkg = getPackageById(id);
|
||||
if (!ourPkg)
|
||||
continue;
|
||||
|
||||
auto existing = ourPkg->existingInstall();
|
||||
if (!existing) {
|
||||
ourPkg->markForInstall();
|
||||
++result;
|
||||
}
|
||||
} // of outer package ID candidates iteration
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CatalogRef Catalog::migratedFrom() const { return m_migratedFrom; }
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <map>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
@@ -93,7 +94,7 @@ public:
|
||||
|
||||
/**
|
||||
* retrieve all the packages in the catalog which are installed
|
||||
* and have a pendig update
|
||||
* and have a pending update
|
||||
*/
|
||||
PackageList packagesNeedingUpdate() const;
|
||||
|
||||
@@ -151,7 +152,32 @@ public:
|
||||
|
||||
bool isUserEnabled() const;
|
||||
void setUserEnabled(bool b);
|
||||
private:
|
||||
|
||||
/**
|
||||
* Given a list of package IDs, mark all which exist in this package,
|
||||
* for installation. ANy packahe IDs not present in this catalog,
|
||||
* will be ignored.
|
||||
*
|
||||
* @result The number for packages newly marked for installation.
|
||||
*/
|
||||
int markPackagesForInstallation(const string_list &packageIds);
|
||||
|
||||
/**
|
||||
* When a catalog is added due to migration, this will contain the
|
||||
* Catalog which triggered the add. Usually this will be a catalog
|
||||
* corresponding to an earlier version.
|
||||
*
|
||||
* Note it's only valid at the time, the migration actually took place;
|
||||
* when the new catalog is loaded from disk, this value will return
|
||||
* null.
|
||||
*
|
||||
* This is intended to allow Uis to show a 'catalog was migrated'
|
||||
* feedback, when they see a catalog refresh, which has a non-null
|
||||
* value of this method.
|
||||
*/
|
||||
CatalogRef migratedFrom() const;
|
||||
|
||||
private:
|
||||
Catalog(Root* aRoot);
|
||||
|
||||
class Downloader;
|
||||
@@ -197,6 +223,8 @@ private:
|
||||
PackageWeakMap m_variantDict;
|
||||
|
||||
function_list<Callback> m_statusCallbacks;
|
||||
|
||||
CatalogRef m_migratedFrom;
|
||||
};
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
@@ -827,7 +827,10 @@ void testVersionMigrateToId(HTTP::Client* cl)
|
||||
|
||||
it = std::find(enabledCats.begin(), enabledCats.end(), altCat);
|
||||
SG_VERIFY(it != enabledCats.end());
|
||||
|
||||
|
||||
SG_CHECK_EQUAL(altCat->packagesNeedingUpdate().size(),
|
||||
1); // should be the 737
|
||||
|
||||
// install a parallel package from the new catalog
|
||||
pkg::PackageRef p2 = root->getPackageById("org.flightgear.test.catalog-alt.b737-NG");
|
||||
SG_CHECK_EQUAL(p2->id(), "b737-NG");
|
||||
@@ -840,8 +843,8 @@ void testVersionMigrateToId(HTTP::Client* cl)
|
||||
pkg::PackageRef p3 = root->getPackageById("b737-NG");
|
||||
SG_CHECK_EQUAL(p2, p3);
|
||||
}
|
||||
|
||||
// test that re-init-ing doesn't mirgate again
|
||||
|
||||
// test that re-init-ing doesn't migrate again
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
|
||||
root->setHTTPClient(cl);
|
||||
@@ -1184,6 +1187,128 @@ void testMirrorsFailure(HTTP::Client* cl)
|
||||
|
||||
}
|
||||
|
||||
void testMigrateInstalled(HTTP::Client *cl) {
|
||||
SGPath rootPath(simgear::Dir::current().path());
|
||||
rootPath.append("pkg_migrate_installed");
|
||||
simgear::Dir pd(rootPath);
|
||||
pd.removeChildren();
|
||||
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
pkg::CatalogRef oldCatalog, newCatalog;
|
||||
|
||||
{
|
||||
oldCatalog = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
pkg::PackageRef p1 =
|
||||
root->getPackageById("org.flightgear.test.catalog1.b747-400");
|
||||
p1->install();
|
||||
auto p2 = root->getPackageById("org.flightgear.test.catalog1.c172p");
|
||||
p2->install();
|
||||
auto p3 = root->getPackageById("org.flightgear.test.catalog1.b737-NG");
|
||||
p3->install();
|
||||
waitForUpdateComplete(cl, root);
|
||||
}
|
||||
|
||||
{
|
||||
newCatalog = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest2/catalog.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
string_list existing;
|
||||
for (const auto &pack : oldCatalog->installedPackages()) {
|
||||
existing.push_back(pack->id());
|
||||
}
|
||||
|
||||
SG_CHECK_EQUAL(4, existing.size());
|
||||
|
||||
int result = newCatalog->markPackagesForInstallation(existing);
|
||||
SG_CHECK_EQUAL(2, result);
|
||||
SG_CHECK_EQUAL(2, newCatalog->packagesNeedingUpdate().size());
|
||||
|
||||
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
|
||||
auto ins = p1->existingInstall();
|
||||
SG_CHECK_EQUAL(0, ins->revsion());
|
||||
}
|
||||
|
||||
{
|
||||
root->scheduleAllUpdates();
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
SG_CHECK_EQUAL(0, newCatalog->packagesNeedingUpdate().size());
|
||||
|
||||
auto p1 = root->getPackageById("org.flightgear.test.catalog2.b737-NG");
|
||||
auto ins = p1->existingInstall();
|
||||
SG_CHECK_EQUAL(ins->revsion(), p1->revision());
|
||||
}
|
||||
}
|
||||
|
||||
void testDontMigrateRemoved(HTTP::Client *cl) {
|
||||
global_catalogVersion = 2; // version which has migration info
|
||||
SGPath rootPath(simgear::Dir::current().path());
|
||||
rootPath.append("cat_dont_migrate_id");
|
||||
simgear::Dir pd(rootPath);
|
||||
pd.removeChildren();
|
||||
|
||||
// install and mnaully remove the alt catalog
|
||||
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest1/catalog-alt.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
root->removeCatalogById("org.flightgear.test.catalog-alt");
|
||||
}
|
||||
|
||||
// install the migration catalog
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "8.1.2"));
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
pkg::CatalogRef c = pkg::Catalog::createFromUrl(
|
||||
root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml");
|
||||
waitForUpdateComplete(cl, root);
|
||||
SG_VERIFY(c->isEnabled());
|
||||
}
|
||||
|
||||
// change version to an alternate one
|
||||
{
|
||||
pkg::RootRef root(new pkg::Root(rootPath, "7.5"));
|
||||
|
||||
auto removed = root->explicitlyRemovedCatalogs();
|
||||
auto j = std::find(removed.begin(), removed.end(),
|
||||
"org.flightgear.test.catalog-alt");
|
||||
SG_VERIFY(j != removed.end());
|
||||
|
||||
root->setHTTPClient(cl);
|
||||
|
||||
// this would tirgger migration, but we blocked it
|
||||
root->refresh(true);
|
||||
waitForUpdateComplete(cl, root);
|
||||
|
||||
pkg::CatalogRef cat = root->getCatalogById("org.flightgear.test.catalog1");
|
||||
SG_VERIFY(!cat->isEnabled());
|
||||
SG_CHECK_EQUAL(cat->status(), pkg::Delegate::FAIL_VERSION);
|
||||
SG_CHECK_EQUAL(cat->id(), "org.flightgear.test.catalog1");
|
||||
SG_CHECK_EQUAL(cat->url(),
|
||||
"http://localhost:2000/catalogTest1/catalog.xml");
|
||||
|
||||
auto enabledCats = root->catalogs();
|
||||
auto it = std::find(enabledCats.begin(), enabledCats.end(), cat);
|
||||
SG_VERIFY(it == enabledCats.end());
|
||||
|
||||
// check the new catalog
|
||||
auto altCat = root->getCatalogById("org.flightgear.test.catalog-alt");
|
||||
SG_VERIFY(altCat.get() == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
sglog().setLogLevels( SG_ALL, SG_WARN );
|
||||
@@ -1228,7 +1353,11 @@ int main(int argc, char* argv[])
|
||||
testInstallBadPackage(&cl);
|
||||
|
||||
testMirrorsFailure(&cl);
|
||||
|
||||
|
||||
testMigrateInstalled(&cl);
|
||||
|
||||
testDontMigrateRemoved(&cl);
|
||||
|
||||
cerr << "Successfully passed all tests!" << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace simgear {
|
||||
namespace pkg {
|
||||
|
||||
Package::Package(const SGPropertyNode* aProps, CatalogRef aCatalog) :
|
||||
m_catalog(aCatalog)
|
||||
m_catalog(aCatalog.get())
|
||||
{
|
||||
initWithProps(aProps);
|
||||
}
|
||||
@@ -198,6 +198,11 @@ InstallRef Package::install()
|
||||
{
|
||||
InstallRef ins = existingInstall();
|
||||
if (ins) {
|
||||
// if there's updates, treat this as a 'start update' request
|
||||
if (ins->hasUpdate()) {
|
||||
m_catalog->root()->scheduleToUpdate(ins);
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
@@ -210,13 +215,39 @@ InstallRef Package::install()
|
||||
return ins;
|
||||
}
|
||||
|
||||
InstallRef Package::markForInstall() {
|
||||
InstallRef ins = existingInstall();
|
||||
if (ins) {
|
||||
return ins;
|
||||
}
|
||||
|
||||
const auto pd = pathOnDisk();
|
||||
|
||||
Dir dir(pd);
|
||||
if (!dir.create(0700)) {
|
||||
SG_LOG(SG_IO, SG_ALERT,
|
||||
"Package::markForInstall: couldn't create directory at:" << pd);
|
||||
return {};
|
||||
}
|
||||
|
||||
ins = new Install{this, pd};
|
||||
_install_cb(this, ins); // not sure if we should trigger the callback for this
|
||||
|
||||
// repeat for dependencies to be kind
|
||||
for (auto dep : dependencies()) {
|
||||
dep->markForInstall();
|
||||
}
|
||||
|
||||
return ins;
|
||||
}
|
||||
|
||||
InstallRef Package::existingInstall(const InstallCallback& cb) const
|
||||
{
|
||||
InstallRef install;
|
||||
try {
|
||||
install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this));
|
||||
} catch (std::exception& ) {
|
||||
return InstallRef();
|
||||
return {};
|
||||
}
|
||||
|
||||
if( cb )
|
||||
@@ -235,6 +266,11 @@ std::string Package::id() const
|
||||
return m_id;
|
||||
}
|
||||
|
||||
CatalogRef Package::catalog() const
|
||||
{
|
||||
return {m_catalog};
|
||||
}
|
||||
|
||||
std::string Package::qualifiedId() const
|
||||
{
|
||||
return m_catalog->id() + "." + id();
|
||||
|
||||
@@ -61,6 +61,13 @@ public:
|
||||
InstallRef
|
||||
existingInstall(const InstallCallback& cb = InstallCallback()) const;
|
||||
|
||||
/**
|
||||
* Mark this package for installation, but don't actually start the
|
||||
* download process. This creates the on-disk placeholder, so
|
||||
* the package will appear an eededing to be updated.
|
||||
*/
|
||||
InstallRef markForInstall();
|
||||
|
||||
bool isInstalled() const;
|
||||
|
||||
/**
|
||||
@@ -130,8 +137,7 @@ public:
|
||||
|
||||
size_t fileSizeBytes() const;
|
||||
|
||||
CatalogRef catalog() const
|
||||
{ return m_catalog; }
|
||||
CatalogRef catalog() const;
|
||||
|
||||
bool matches(const SGPropertyNode* aFilter) const;
|
||||
|
||||
@@ -236,7 +242,7 @@ private:
|
||||
SGPropertyNode_ptr m_props;
|
||||
std::string m_id;
|
||||
string_set m_tags;
|
||||
CatalogRef m_catalog;
|
||||
Catalog* m_catalog = nullptr; // non-owning ref, explicitly
|
||||
string_list m_variants;
|
||||
|
||||
mutable function_list<InstallCallback> _install_cb;
|
||||
|
||||
@@ -133,7 +133,9 @@ public:
|
||||
|
||||
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
|
||||
{
|
||||
for (auto d : delegates) {
|
||||
// take a copy of delegates since firing this can modify the data
|
||||
const auto currentDelegates = delegates;
|
||||
for (auto d : currentDelegates) {
|
||||
d->catalogRefreshed(catalog, status);
|
||||
}
|
||||
}
|
||||
@@ -283,6 +285,32 @@ public:
|
||||
fireDataForThumbnail(url, reinterpret_cast<const uint8_t*>(bytes.data()), bytes.size());
|
||||
}
|
||||
|
||||
void writeRemovedCatalogsFile() const {
|
||||
SGPath p = path / "RemovedCatalogs";
|
||||
sg_ofstream stream(p, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
for (const auto &cid : manuallyRemovedCatalogs) {
|
||||
stream << cid << "\n";
|
||||
}
|
||||
stream.close();
|
||||
}
|
||||
|
||||
void loadRemovedCatalogsFile() {
|
||||
manuallyRemovedCatalogs.clear();
|
||||
SGPath p = path / "RemovedCatalogs";
|
||||
if (!p.exists())
|
||||
return;
|
||||
|
||||
sg_ifstream stream(p, std::ios::in);
|
||||
while (!stream.eof()) {
|
||||
std::string line;
|
||||
std::getline(stream, line);
|
||||
const auto trimmed = strutils::strip(line);
|
||||
if (!trimmed.empty()) {
|
||||
manuallyRemovedCatalogs.push_back(trimmed);
|
||||
}
|
||||
} // of lines iteration
|
||||
}
|
||||
|
||||
DelegateVec delegates;
|
||||
|
||||
SGPath path;
|
||||
@@ -312,6 +340,9 @@ public:
|
||||
|
||||
typedef std::map<PackageRef, InstallRef> InstallCache;
|
||||
InstallCache m_installs;
|
||||
|
||||
/// persistent list of catalogs the user has manually removed
|
||||
string_list manuallyRemovedCatalogs;
|
||||
};
|
||||
|
||||
|
||||
@@ -400,6 +431,8 @@ Root::Root(const SGPath& aPath, const std::string& aVersion) :
|
||||
thumbsCacheDir.create(0755);
|
||||
}
|
||||
|
||||
d->loadRemovedCatalogsFile();
|
||||
|
||||
for (SGPath c : dir.children(Dir::TYPE_DIR | Dir::NO_DOT_OR_DOTDOT)) {
|
||||
// note this will set the catalog status, which will insert into
|
||||
// disabled catalogs automatically if necesary
|
||||
@@ -621,6 +654,13 @@ void Root::scheduleToUpdate(InstallRef aInstall)
|
||||
}
|
||||
}
|
||||
|
||||
void Root::scheduleAllUpdates() {
|
||||
auto toBeUpdated = packagesNeedingUpdate(); // make a copy
|
||||
for (const auto &u : toBeUpdated) {
|
||||
scheduleToUpdate(u->existingInstall());
|
||||
}
|
||||
}
|
||||
|
||||
bool Root::isInstallQueued(InstallRef aInstall) const
|
||||
{
|
||||
auto it = std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
|
||||
@@ -684,20 +724,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
|
||||
auto catIt = d->catalogs.find(aCat->id());
|
||||
d->fireRefreshStatus(aCat, aReason);
|
||||
|
||||
if (aReason == Delegate::STATUS_IN_PROGRESS) {
|
||||
d->refreshing.insert(aCat);
|
||||
} else {
|
||||
d->refreshing.erase(aCat);
|
||||
}
|
||||
|
||||
if ((aReason == Delegate::STATUS_REFRESHED) && (catIt == d->catalogs.end())) {
|
||||
if (aCat->isUserEnabled() &&
|
||||
(aReason == Delegate::STATUS_REFRESHED) &&
|
||||
(catIt == d->catalogs.end()))
|
||||
{
|
||||
assert(!aCat->id().empty());
|
||||
d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
|
||||
|
||||
// catalog might have been previously disabled, let's remove in that case
|
||||
// catalog might have been previously disabled, let's remove in that case
|
||||
auto j = std::find(d->disabledCatalogs.begin(),
|
||||
d->disabledCatalogs.end(),
|
||||
aCat);
|
||||
d->disabledCatalogs.end(),
|
||||
aCat);
|
||||
if (j != d->disabledCatalogs.end()) {
|
||||
SG_LOG(SG_GENERAL, SG_INFO, "re-enabling disabled catalog:" << aCat->id());
|
||||
d->disabledCatalogs.erase(j);
|
||||
@@ -705,7 +742,7 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
|
||||
}
|
||||
|
||||
if (!aCat->isEnabled()) {
|
||||
// catalog has errors, disable it
|
||||
// catalog has errors or was disabled by user, disable it
|
||||
auto j = std::find(d->disabledCatalogs.begin(),
|
||||
d->disabledCatalogs.end(),
|
||||
aCat);
|
||||
@@ -720,6 +757,17 @@ void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
|
||||
}
|
||||
} // of catalog is disabled
|
||||
|
||||
// remove from refreshing /after/ checking for enable / disabled, since for
|
||||
// new catalogs, the reference in d->refreshing might be our /only/
|
||||
// reference to the catalog. Once the refresh is done (either failed or
|
||||
// succeeded) the Catalog will be in either d->catalogs or
|
||||
// d->disabledCatalogs
|
||||
if (aReason == Delegate::STATUS_IN_PROGRESS) {
|
||||
d->refreshing.insert(aCat);
|
||||
} else {
|
||||
d->refreshing.erase(aCat);
|
||||
}
|
||||
|
||||
if (d->refreshing.empty()) {
|
||||
d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
|
||||
d->firePackagesChanged();
|
||||
@@ -780,6 +828,9 @@ bool Root::removeCatalogById(const std::string& aId)
|
||||
<< "failed to remove directory");
|
||||
}
|
||||
|
||||
d->manuallyRemovedCatalogs.push_back(aId);
|
||||
d->writeRemovedCatalogsFile();
|
||||
|
||||
// notify that a catalog is being removed
|
||||
d->firePackagesChanged();
|
||||
|
||||
@@ -851,6 +902,10 @@ void Root::unregisterInstall(InstallRef ins)
|
||||
d->fireFinishUninstall(ins->package());
|
||||
}
|
||||
|
||||
string_list Root::explicitlyRemovedCatalogs() const {
|
||||
return d->manuallyRemovedCatalogs;
|
||||
}
|
||||
|
||||
} // of namespace pkg
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
@@ -155,7 +155,22 @@ public:
|
||||
void requestThumbnailData(const std::string& aUrl);
|
||||
|
||||
bool isInstallQueued(InstallRef aInstall) const;
|
||||
private:
|
||||
|
||||
/**
|
||||
* Mark all 'to be updated' packages for update now
|
||||
*/
|
||||
void scheduleAllUpdates();
|
||||
|
||||
/**
|
||||
* @brief list of catalog IDs, the user has explicitly removed via
|
||||
* removeCatalogById(). This is important to allow the user to opt-out
|
||||
* of migrated packages.
|
||||
*
|
||||
* This information is stored in a helper file, in the root directory
|
||||
*/
|
||||
string_list explicitlyRemovedCatalogs() const;
|
||||
|
||||
private:
|
||||
friend class Install;
|
||||
friend class Catalog;
|
||||
friend class Package;
|
||||
|
||||
86
simgear/package/catalogTest2/catalog.xml
Normal file
86
simgear/package/catalogTest2/catalog.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<PropertyList>
|
||||
<id>org.flightgear.test.catalog2</id>
|
||||
<description>Second test catalog</description>
|
||||
<url>http://localhost:2000/catalogTest2/catalog.xml</url>
|
||||
<catalog-version>4</catalog-version>
|
||||
|
||||
<version>8.1.*</version>
|
||||
<version>8.0.0</version>
|
||||
<version>8.2.0</version>
|
||||
|
||||
<package>
|
||||
<id>alpha</id>
|
||||
<name>Alpha package</name>
|
||||
<revision type="int">8</revision>
|
||||
<file-size-bytes type="int">593</file-size-bytes>
|
||||
|
||||
<md5>a469c4b837f0521db48616cfe65ac1ea</md5>
|
||||
<url>http://localhost:2000/catalogTest1/alpha.zip</url>
|
||||
|
||||
<dir>alpha</dir>
|
||||
|
||||
</package>
|
||||
|
||||
|
||||
<package>
|
||||
<id>b737-NG</id>
|
||||
<name>Boeing 737 NG</name>
|
||||
<dir>b737NG</dir>
|
||||
<description>A popular twin-engined narrow body jet</description>
|
||||
<revision type="int">111</revision>
|
||||
<file-size-bytes type="int">860</file-size-bytes>
|
||||
|
||||
<tag>boeing</tag>
|
||||
<tag>jet</tag>
|
||||
<tag>ifr</tag>
|
||||
|
||||
<!-- not within a localized element -->
|
||||
<de>
|
||||
<description>German description of B737NG XYZ</description>
|
||||
</de>
|
||||
<fr>
|
||||
<description>French description of B737NG</description>
|
||||
</fr>
|
||||
|
||||
<rating>
|
||||
<FDM type="int">5</FDM>
|
||||
<systems type="int">5</systems>
|
||||
<model type="int">4</model>
|
||||
<cockpit type="int">4</cockpit>
|
||||
</rating>
|
||||
|
||||
<md5>a94ca5704f305b90767f40617d194ed6</md5>
|
||||
<url>http://localhost:2000/mirrorA/b737.tar.gz</url>
|
||||
<url>http://localhost:2000/mirrorB/b737.tar.gz</url>
|
||||
<url>http://localhost:2000/mirrorC/b737.tar.gz</url>
|
||||
</package>
|
||||
|
||||
<package>
|
||||
<id>b747-400</id>
|
||||
<name>Boeing 747-400</name>
|
||||
<dir>b744</dir>
|
||||
<description>A popular four-engined wide-body jet</description>
|
||||
<revision type="int">111</revision>
|
||||
<file-size-bytes type="int">860</file-size-bytes>
|
||||
|
||||
<tag>boeing</tag>
|
||||
<tag>jet</tag>
|
||||
<tag>ifr</tag>
|
||||
|
||||
|
||||
|
||||
<rating>
|
||||
<FDM type="int">5</FDM>
|
||||
<systems type="int">5</systems>
|
||||
<model type="int">4</model>
|
||||
<cockpit type="int">4</cockpit>
|
||||
</rating>
|
||||
|
||||
<md5>4d3f7417d74f811aa20ccc4f35673d20</md5>
|
||||
<!-- this URL will sometimes fail, on purpose -->
|
||||
<url>http://localhost:2000/catalogTest1/b747.tar.gz</url>
|
||||
</package>
|
||||
|
||||
</PropertyList>
|
||||
@@ -59,6 +59,12 @@ void AtomicChangeListener::fireChangeListeners()
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
void AtomicChangeListener::clearPendingChanges()
|
||||
{
|
||||
auto& listeners = ListenerListSingleton::instance()->listeners;
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
void AtomicChangeListener::valueChangedImplementation()
|
||||
{
|
||||
if (!_dirty) {
|
||||
|
||||
@@ -52,7 +52,17 @@ public:
|
||||
bool isDirty() { return _dirty; }
|
||||
bool isValid() { return _valid; }
|
||||
virtual void unregister_property(SGPropertyNode* node) override;
|
||||
|
||||
static void fireChangeListeners();
|
||||
|
||||
/**
|
||||
* @brief Ensure we've deleted any pending changes.
|
||||
*
|
||||
* This is important in shutdown and reset, to avoid holding
|
||||
* property listeners around after the property tree is destroyed
|
||||
*/
|
||||
static void clearPendingChanges();
|
||||
|
||||
private:
|
||||
virtual void valueChangedImplementation() override;
|
||||
virtual void valuesChanged();
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <exception> // can't use sg_exception becuase of PROPS_STANDALONE
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -48,6 +52,22 @@ using std::stringstream;
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
|
||||
struct SGPropertyNodeListeners
|
||||
{
|
||||
/* Protect _num_iterators and _items. We use a recursive mutex to allow
|
||||
nested access to work as normal. */
|
||||
std::recursive_mutex _rmutex;
|
||||
|
||||
/* This keeps a count of the current number of nested invocations of
|
||||
forEachListener(). If non-zero, other code higher up the stack is iterating
|
||||
_items[] so for example code must not erase items in the vector. */
|
||||
int _num_iterators = 0;
|
||||
|
||||
std::vector<SGPropertyChangeListener *> _items;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Local classes.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
@@ -546,23 +566,15 @@ find_node (SGPropertyNode * current,
|
||||
}
|
||||
}
|
||||
#else
|
||||
template<typename Range>
|
||||
SGPropertyNode*
|
||||
find_node (SGPropertyNode * current,
|
||||
const Range& path,
|
||||
bool create,
|
||||
int last_index = -1)
|
||||
{
|
||||
template <typename Range>
|
||||
SGPropertyNode *find_node(SGPropertyNode *current, const Range &path,
|
||||
bool create, int last_index = -1) {
|
||||
using namespace boost;
|
||||
typedef split_iterator<typename range_result_iterator<Range>::type>
|
||||
PathSplitIterator;
|
||||
|
||||
PathSplitIterator itr
|
||||
= make_split_iterator(path, first_finder("/", is_equal()));
|
||||
auto itr = make_split_iterator(path, first_finder("/", is_equal()));
|
||||
if (*path.begin() == '/')
|
||||
return find_node_aux(current->getRootNode(), itr, create, last_index);
|
||||
else
|
||||
return find_node_aux(current, itr, create, last_index);
|
||||
else
|
||||
return find_node_aux(current, itr, create, last_index);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -968,7 +980,7 @@ SGPropertyNode::~SGPropertyNode ()
|
||||
|
||||
if (_listeners) {
|
||||
vector<SGPropertyChangeListener*>::iterator it;
|
||||
for (it = _listeners->begin(); it != _listeners->end(); ++it)
|
||||
for (it = _listeners->_items.begin(); it != _listeners->_items.end(); ++it)
|
||||
(*it)->unregister_property(this);
|
||||
delete _listeners;
|
||||
}
|
||||
@@ -2392,8 +2404,22 @@ SGPropertyNode::addChangeListener (SGPropertyChangeListener * listener,
|
||||
bool initial)
|
||||
{
|
||||
if (_listeners == 0)
|
||||
_listeners = new vector<SGPropertyChangeListener*>;
|
||||
_listeners->push_back(listener);
|
||||
_listeners = new SGPropertyNodeListeners;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
/* If there's a nullptr entry (a listener that was unregistered), we
|
||||
overwrite it. This ensures that listeners that routinely unregister+register
|
||||
themselves don't make _listeners->_items grow unnecessarily. Otherwise simply
|
||||
append. */
|
||||
auto it = std::find(_listeners->_items.begin(), _listeners->_items.end(),
|
||||
(SGPropertyChangeListener*) nullptr
|
||||
);
|
||||
if (it == _listeners->_items.end()) {
|
||||
_listeners->_items.push_back(listener);
|
||||
}
|
||||
else {
|
||||
*it = listener;
|
||||
}
|
||||
listener->register_property(this);
|
||||
if (initial)
|
||||
listener->valueChanged(this);
|
||||
@@ -2404,15 +2430,35 @@ SGPropertyNode::removeChangeListener (SGPropertyChangeListener * listener)
|
||||
{
|
||||
if (_listeners == 0)
|
||||
return;
|
||||
/* We use a std::unique_lock rather than a std::lock_guard because we may
|
||||
need to unlock early. */
|
||||
std::unique_lock<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
vector<SGPropertyChangeListener*>::iterator it =
|
||||
find(_listeners->begin(), _listeners->end(), listener);
|
||||
if (it != _listeners->end()) {
|
||||
_listeners->erase(it);
|
||||
listener->unregister_property(this);
|
||||
if (_listeners->empty()) {
|
||||
vector<SGPropertyChangeListener*>* tmp = _listeners;
|
||||
_listeners = 0;
|
||||
delete tmp;
|
||||
find(_listeners->_items.begin(), _listeners->_items.end(), listener);
|
||||
if (it != _listeners->_items.end()) {
|
||||
assert(_listeners->_num_iterators >= 0);
|
||||
if (_listeners->_num_iterators) {
|
||||
/* _listeners._items is currently being iterated further up the stack in
|
||||
this thread by one or more nested invocations of forEachListener(), so
|
||||
we must be careful to not break these iterators. So we must not delete
|
||||
_listeners. And we must not erase this entry from the vector because that
|
||||
could cause the next listener to be missed out.
|
||||
|
||||
So we simply set this entry to nullptr (nullptr items are skipped by
|
||||
forEachListener()). When all invocations of forEachListener() have
|
||||
finished iterating and it is safe to modify things again, it will clean
|
||||
up any nullptr entries in _listeners->_items. */
|
||||
*it = nullptr;
|
||||
listener->unregister_property(this);
|
||||
}
|
||||
else {
|
||||
_listeners->_items.erase(it);
|
||||
listener->unregister_property(this);
|
||||
if (_listeners->_items.empty()) {
|
||||
lock.unlock();
|
||||
delete _listeners;
|
||||
_listeners = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2461,15 +2507,72 @@ SGPropertyNode::fireChildrenRemovedRecursive()
|
||||
}
|
||||
}
|
||||
|
||||
/* Calls <callback> for each item in _listeners. We are careful to skip nullptr
|
||||
entries in _listeners->items[], which can be created if listeners are removed
|
||||
while we are iterating. */
|
||||
static void forEachListener(
|
||||
SGPropertyNode* node,
|
||||
SGPropertyNodeListeners*& _listeners,
|
||||
std::function<void (SGPropertyChangeListener*)> callback
|
||||
)
|
||||
{
|
||||
if (!_listeners) return;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
assert(_listeners->_num_iterators >= 0);
|
||||
_listeners->_num_iterators += 1;
|
||||
|
||||
/* We need to use an index here when iterating _listeners->_items, not an
|
||||
iterator. This is because a listener may add new listeners, causing the
|
||||
vector to be reallocated, which would invalidate any iterator. */
|
||||
for (size_t i = 0; i < _listeners->_items.size(); ++i) {
|
||||
auto listener = _listeners->_items[i];
|
||||
if (listener) {
|
||||
try {
|
||||
callback(listener);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "Ignoring exception from property callback: " << e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_listeners->_num_iterators -= 1;
|
||||
assert(_listeners->_num_iterators >= 0);
|
||||
|
||||
if (_listeners->_num_iterators == 0) {
|
||||
|
||||
/* Remove any items that have been set to nullptr. */
|
||||
_listeners->_items.erase(
|
||||
std::remove(_listeners->_items.begin(), _listeners->_items.end(), (SGPropertyChangeListener*) nullptr),
|
||||
_listeners->_items.end()
|
||||
);
|
||||
if (_listeners->_items.empty()) {
|
||||
delete _listeners;
|
||||
_listeners = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int SGPropertyNode::nListeners() const
|
||||
{
|
||||
if (!_listeners) return 0;
|
||||
std::lock_guard<std::recursive_mutex> lock(_listeners->_rmutex);
|
||||
int n = 0;
|
||||
for (auto listener: _listeners->_items) {
|
||||
if (listener) n += 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
SGPropertyNode::fireValueChanged (SGPropertyNode * node)
|
||||
{
|
||||
if (_listeners != 0) {
|
||||
for (unsigned int i = 0; i < _listeners->size(); i++) {
|
||||
if ((*_listeners)[i])
|
||||
(*_listeners)[i]->valueChanged(node);
|
||||
}
|
||||
}
|
||||
forEachListener(
|
||||
node,
|
||||
_listeners,
|
||||
[&](SGPropertyChangeListener* listener) { listener->valueChanged(node);}
|
||||
);
|
||||
if (_parent != 0)
|
||||
_parent->fireValueChanged(node);
|
||||
}
|
||||
@@ -2478,11 +2581,11 @@ void
|
||||
SGPropertyNode::fireChildAdded (SGPropertyNode * parent,
|
||||
SGPropertyNode * child)
|
||||
{
|
||||
if (_listeners != 0) {
|
||||
for (unsigned int i = 0; i < _listeners->size(); i++) {
|
||||
(*_listeners)[i]->childAdded(parent, child);
|
||||
}
|
||||
}
|
||||
forEachListener(
|
||||
parent,
|
||||
_listeners,
|
||||
[&](SGPropertyChangeListener* listener) { listener->childAdded(parent, child);}
|
||||
);
|
||||
if (_parent != 0)
|
||||
_parent->fireChildAdded(parent, child);
|
||||
}
|
||||
@@ -2491,11 +2594,11 @@ void
|
||||
SGPropertyNode::fireChildRemoved (SGPropertyNode * parent,
|
||||
SGPropertyNode * child)
|
||||
{
|
||||
if (_listeners != 0) {
|
||||
for (unsigned int i = 0; i < _listeners->size(); i++) {
|
||||
(*_listeners)[i]->childRemoved(parent, child);
|
||||
}
|
||||
}
|
||||
forEachListener(
|
||||
parent,
|
||||
_listeners,
|
||||
[&](SGPropertyChangeListener* listener) { listener->childRemoved(parent, child);}
|
||||
);
|
||||
if (_parent != 0)
|
||||
_parent->fireChildRemoved(parent, child);
|
||||
}
|
||||
|
||||
@@ -759,6 +759,9 @@ private:
|
||||
};
|
||||
|
||||
|
||||
struct SGPropertyNodeListeners;
|
||||
|
||||
|
||||
/**
|
||||
* A node in a property tree.
|
||||
*/
|
||||
@@ -1713,7 +1716,7 @@ public:
|
||||
/**
|
||||
* Get the number of listeners.
|
||||
*/
|
||||
int nListeners () const { return _listeners ? (int)_listeners->size() : 0; }
|
||||
int nListeners () const;
|
||||
|
||||
|
||||
/**
|
||||
@@ -1847,7 +1850,7 @@ private:
|
||||
char * string_val;
|
||||
} _local_val;
|
||||
|
||||
std::vector<SGPropertyChangeListener *> * _listeners;
|
||||
SGPropertyNodeListeners* _listeners;
|
||||
|
||||
// Pass name as a pair of iterators
|
||||
template<typename Itr>
|
||||
@@ -2194,11 +2197,12 @@ public:
|
||||
_property->addChangeListener(this,initial);
|
||||
}
|
||||
|
||||
SGPropertyChangeCallback(const SGPropertyChangeCallback<T>& other) :
|
||||
_obj(other._obj), _callback(other._callback), _property(other._property)
|
||||
{
|
||||
_property->addChangeListener(this,false);
|
||||
}
|
||||
SGPropertyChangeCallback(const SGPropertyChangeCallback<T>& other)
|
||||
: SGPropertyChangeListener(other),
|
||||
_obj(other._obj), _callback(other._callback), _property(other._property)
|
||||
{
|
||||
_property->addChangeListener(this,false);
|
||||
}
|
||||
|
||||
virtual ~SGPropertyChangeCallback()
|
||||
{
|
||||
|
||||
@@ -854,4 +854,48 @@ copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// for normal recursion we want to call the predicate *before* creating children
|
||||
bool _inner_copyPropertiesIf(const SGPropertyNode *in, SGPropertyNode *out,
|
||||
PropertyPredicate predicate)
|
||||
{
|
||||
bool retval = copyPropertyValue(in, out);
|
||||
if (!retval) {
|
||||
return false;
|
||||
}
|
||||
out->setAttributes( in->getAttributes() );
|
||||
|
||||
int nChildren = in->nChildren();
|
||||
for (int i = 0; i < nChildren; i++) {
|
||||
const SGPropertyNode* in_child = in->getChild(i);
|
||||
// skip this child
|
||||
if (!predicate(in_child)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SGPropertyNode* out_child = out->getChild(in_child->getNameString(),
|
||||
in_child->getIndex(),
|
||||
true);
|
||||
|
||||
bool ok = copyPropertiesIf(in_child, out_child, predicate);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
} // of children iteration
|
||||
return true;
|
||||
}
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
bool copyPropertiesIf(const SGPropertyNode *in, SGPropertyNode *out,
|
||||
PropertyPredicate predicate)
|
||||
{
|
||||
// allow the *entire* copy to be a no-op
|
||||
bool doCopy = predicate(in);
|
||||
if (!doCopy)
|
||||
return true; // doesn't count as failure
|
||||
|
||||
return _inner_copyPropertiesIf(in, out, predicate);
|
||||
}
|
||||
// end of props_io.cxx
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <iosfwd>
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* Read properties from an XML input stream.
|
||||
@@ -67,6 +68,16 @@ bool copyProperties (const SGPropertyNode *in, SGPropertyNode *out);
|
||||
bool copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
|
||||
SGPropertyNode::Attribute attr);
|
||||
|
||||
using PropertyPredicate = std::function<bool (const SGPropertyNode* in)>;
|
||||
|
||||
/**
|
||||
* Copy properties, if the predicate returns true for the in node.
|
||||
* If a parent node returns false, descendats will <em>not</em> be
|
||||
* checked
|
||||
*/
|
||||
bool copyPropertiesIf(const SGPropertyNode *in, SGPropertyNode *out,
|
||||
PropertyPredicate predicate);
|
||||
|
||||
#endif // __PROPS_IO_HXX
|
||||
|
||||
// end of props_io.hxx
|
||||
|
||||
@@ -451,13 +451,17 @@ void defineSamplePropertyTree(SGPropertyNode_ptr root)
|
||||
class TestListener : public SGPropertyChangeListener
|
||||
{
|
||||
public:
|
||||
TestListener(SGPropertyNode* root, bool recursive = false) :
|
||||
_root(root) {}
|
||||
TestListener(SGPropertyNode* root, bool recursive = false, bool self_unregister = false) :
|
||||
_root(root), _self_unregister(self_unregister) {}
|
||||
|
||||
virtual void valueChanged(SGPropertyNode* node) override
|
||||
{
|
||||
std::cout << "saw value changed for:" << node->getPath() << std::endl;
|
||||
valueChangeCount[node]++;
|
||||
if (_self_unregister) {
|
||||
std::cout << "valueChanged(): calling removeChangeListener() to self-remove\n";
|
||||
node->removeChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
int checkValueChangeCount(const std::string& path) const
|
||||
@@ -533,6 +537,7 @@ public:
|
||||
}
|
||||
private:
|
||||
SGPropertyNode* _root;
|
||||
bool _self_unregister;
|
||||
std::map<SGPropertyNode*, unsigned int> valueChangeCount;
|
||||
|
||||
std::vector<ParentChange> adds;
|
||||
@@ -942,6 +947,13 @@ void testDeleterListener()
|
||||
SG_VERIFY(ensureNListeners(tree, 0));
|
||||
}
|
||||
|
||||
// Self-unregister. prior to 2019-07-08 this would segv.
|
||||
{
|
||||
std::shared_ptr<TestListener> l1( new TestListener(tree.get(), true /* recursive */, true /*self_unregister*/));
|
||||
tree->setFloatValue("position/body/sub/self-unregister", 0.1);
|
||||
tree->getNode("position/body/sub/self-unregister")->addChangeListener(l1.get());
|
||||
tree->setFloatValue("position/body/sub/self-unregister", 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
int main (int ac, char ** av)
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
#include <simgear/structure/SGExpression.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/vectorPropTemplates.hxx>
|
||||
|
||||
#include <simgear/debug/ErrorReportingCallback.hxx>
|
||||
#include <simgear/io/iostreams/sgstream.hxx>
|
||||
|
||||
namespace simgear
|
||||
@@ -928,7 +928,8 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
// FIXME orig: const string& shaderName = shaderKey.first;
|
||||
string shaderName = shaderKey.first;
|
||||
Shader::Type stype = (Shader::Type)shaderKey.second;
|
||||
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false) &&
|
||||
const bool compositorEnabled = getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false);
|
||||
if (compositorEnabled &&
|
||||
shaderName.substr(0, shaderName.find("/")) == "Shaders") {
|
||||
shaderName = "Compositor/" + shaderName;
|
||||
}
|
||||
@@ -936,7 +937,9 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
|
||||
if (fileName.empty())
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Could not locate shader" << shaderName);
|
||||
|
||||
if (!compositorEnabled) {
|
||||
reportError("Missing shader", shaderName);
|
||||
}
|
||||
|
||||
throw BuilderException(string("couldn't find shader ") +
|
||||
shaderName);
|
||||
@@ -1481,10 +1484,8 @@ void mergeSchemesFallbacks(Effect *effect, const SGReaderWriterOptions *options)
|
||||
|
||||
// Walk the techniques property tree, building techniques and
|
||||
// passes.
|
||||
static std::mutex realizeTechniques_lock;
|
||||
bool Effect::realizeTechniques(const SGReaderWriterOptions* options)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(realizeTechniques_lock);
|
||||
if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false))
|
||||
mergeSchemesFallbacks(this, options);
|
||||
|
||||
|
||||
@@ -273,11 +273,19 @@ bool setAttrs(const TexTuple& attrs, Texture* tex,
|
||||
options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS_NORMALIZED);
|
||||
else
|
||||
options->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS);
|
||||
#if OSG_VERSION_LESS_THAN(3,4,2)
|
||||
result = osgDB::readImageFile(imageName, options);
|
||||
#else
|
||||
result = osgDB::readRefImageFile(imageName, options);
|
||||
#endif
|
||||
|
||||
try {
|
||||
#if OSG_VERSION_LESS_THAN(3,4,2)
|
||||
result = osgDB::readImageFile(imageName, options);
|
||||
#else
|
||||
result = osgDB::readRefImageFile(imageName, options);
|
||||
#endif
|
||||
} catch (std::bad_alloc& ba) {
|
||||
SG_LOG(SG_GL, SG_ALERT, "Bad allocation loading:" << imageName);
|
||||
// todo: report low memory warning
|
||||
return false;
|
||||
}
|
||||
|
||||
options->setLoadOriginHint(origLOH);
|
||||
osg::ref_ptr<osg::Image> image;
|
||||
if (result.success())
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <osg/AlphaFunc>
|
||||
#include <osg/Group>
|
||||
@@ -103,37 +103,39 @@ SGMatModel::get_model_count( SGPropertyNode *prop_root )
|
||||
inline void
|
||||
SGMatModel::load_models( SGPropertyNode *prop_root )
|
||||
{
|
||||
// Load model only on demand
|
||||
if (!_models_loaded) {
|
||||
for (unsigned int i = 0; i < _paths.size(); i++) {
|
||||
osg::Node *entity = SGModelLib::loadModel(_paths[i], prop_root);
|
||||
if (entity != 0) {
|
||||
// FIXME: this stuff can be handled
|
||||
// in the XML wrapper as well (at least,
|
||||
// the billboarding should be handled
|
||||
// there).
|
||||
|
||||
if (_heading_type == HEADING_BILLBOARD) {
|
||||
// if the model is a billboard, it is likely :
|
||||
// 1. a branch with only leaves,
|
||||
// 2. a tree or a non rectangular shape faked by transparency
|
||||
// We add alpha clamp then
|
||||
osg::StateSet* stateSet = entity->getOrCreateStateSet();
|
||||
osg::AlphaFunc* alphaFunc =
|
||||
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01f);
|
||||
stateSet->setAttributeAndModes(alphaFunc,
|
||||
osg::StateAttribute::OVERRIDE);
|
||||
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||
}
|
||||
|
||||
_models.push_back(entity);
|
||||
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
|
||||
// Ensure the vector contains something, otherwise get_random_model below fails
|
||||
_models.push_back(new osg::Node());
|
||||
}
|
||||
}
|
||||
std::lock_guard<std::mutex> g(_loadMutex);
|
||||
|
||||
// Load model only on demand
|
||||
if (!_models_loaded) {
|
||||
for (unsigned int i = 0; i < _paths.size(); i++) {
|
||||
osg::Node* entity = SGModelLib::loadModel(_paths[i], prop_root);
|
||||
if (entity != 0) {
|
||||
// FIXME: this stuff can be handled
|
||||
// in the XML wrapper as well (at least,
|
||||
// the billboarding should be handled
|
||||
// there).
|
||||
|
||||
if (_heading_type == HEADING_BILLBOARD) {
|
||||
// if the model is a billboard, it is likely :
|
||||
// 1. a branch with only leaves,
|
||||
// 2. a tree or a non rectangular shape faked by transparency
|
||||
// We add alpha clamp then
|
||||
osg::StateSet* stateSet = entity->getOrCreateStateSet();
|
||||
osg::AlphaFunc* alphaFunc =
|
||||
new osg::AlphaFunc(osg::AlphaFunc::GREATER, 0.01f);
|
||||
stateSet->setAttributeAndModes(alphaFunc,
|
||||
osg::StateAttribute::OVERRIDE);
|
||||
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
|
||||
}
|
||||
|
||||
_models.push_back(entity);
|
||||
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_ALERT, "Failed to load object " << _paths[i]);
|
||||
// Ensure the vector contains something, otherwise get_random_model below fails
|
||||
_models.push_back(new osg::Node());
|
||||
}
|
||||
}
|
||||
}
|
||||
_models_loaded = true;
|
||||
}
|
||||
|
||||
@@ -149,6 +149,8 @@ private:
|
||||
double _spacing_m;
|
||||
double _range_m;
|
||||
HeadingType _heading_type;
|
||||
|
||||
std::mutex _loadMutex;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -82,13 +82,13 @@ void _writeColor(GLenum pixelFormat, T* data, float scale, osg::Vec4 value)
|
||||
switch(pixelFormat)
|
||||
{
|
||||
case(GL_DEPTH_COMPONENT): //intentionally fall through and execute the code for GL_LUMINANCE
|
||||
case(GL_LUMINANCE): { *data++ = value.r()*scale; break; }
|
||||
case(GL_ALPHA): { *data++ = value.a()*scale; break; }
|
||||
case(GL_LUMINANCE_ALPHA): { *data++ = value.r()*scale; *data++ = value.a()*scale; break; }
|
||||
case(GL_RGB): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; break; }
|
||||
case(GL_RGBA): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; *data++ = value.a()*scale; break; }
|
||||
case(GL_BGR): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; break; }
|
||||
case(GL_BGRA): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; *data++ = value.a()*scale; break; }
|
||||
case(GL_LUMINANCE): { *data = value.r()*scale; break; }
|
||||
case(GL_ALPHA): { *data = value.a()*scale; break; }
|
||||
case(GL_LUMINANCE_ALPHA): { *data++ = value.r()*scale; *data = value.a()*scale; break; }
|
||||
case(GL_RGB): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data = value.b()*scale; break; }
|
||||
case(GL_RGBA): { *data++ = value.r()*scale; *data++ = value.g()*scale; *data++ = value.b()*scale; *data = value.a()*scale; break; }
|
||||
case(GL_BGR): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data = value.r()*scale; break; }
|
||||
case(GL_BGRA): { *data++ = value.b()*scale; *data++ = value.g()*scale; *data++ = value.r()*scale; *data = value.a()*scale; break; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,13 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
|
||||
{
|
||||
bool computeMipmap = false;
|
||||
unsigned int nbComponents = osg::Image::computeNumComponents( image->getPixelFormat() );
|
||||
if ( std::get<0>(attrs) != AUTOMATIC &&
|
||||
int s = image->s();
|
||||
int t = image->t();
|
||||
if ( (s & (s - 1)) || (t & (t - 1)) ) // power of two test
|
||||
{
|
||||
SG_LOG(SG_IO, SG_DEV_ALERT, "mipmapping: texture size not a power-of-two: " + image->getFileName());
|
||||
}
|
||||
else if ( std::get<0>(attrs) != AUTOMATIC &&
|
||||
( std::get<1>(attrs) != AUTOMATIC || nbComponents < 2 ) &&
|
||||
( std::get<2>(attrs) != AUTOMATIC || nbComponents < 3 ) &&
|
||||
( std::get<3>(attrs) != AUTOMATIC || nbComponents < 4 ) )
|
||||
@@ -283,9 +289,7 @@ osg::Image* computeMipmap( osg::Image* image, MipMapTuple attrs )
|
||||
if ( computeMipmap )
|
||||
{
|
||||
osg::ref_ptr<osg::Image> mipmaps = new osg::Image();
|
||||
int s = image->s(),
|
||||
t = image->t(),
|
||||
r = image->r();
|
||||
int r = image->r();
|
||||
int nb = osg::Image::computeNumberOfMipmapLevels(s, t, r);
|
||||
osg::Image::MipmapDataType mipmapOffsets;
|
||||
unsigned int offset = 0;
|
||||
|
||||
@@ -359,6 +359,8 @@ ModelRegistry::readImage(const string& fileName,
|
||||
if (res.validImage()) {
|
||||
osg::ref_ptr<osg::Image> srcImage = res.getImage();
|
||||
int width = srcImage->s();
|
||||
//int packing = srcImage->getPacking();
|
||||
//printf("packing %d format %x pixel size %d InternalTextureFormat %x\n", packing, srcImage->getPixelFormat(), srcImage->getPixelSizeInBits(), srcImage->getInternalTextureFormat() );
|
||||
bool transparent = srcImage->isImageTranslucent();
|
||||
bool isNormalMap = false;
|
||||
bool isEffect = false;
|
||||
@@ -367,6 +369,11 @@ ModelRegistry::readImage(const string& fileName,
|
||||
*/
|
||||
bool can_compress = (transparent && compress_transparent) || (!transparent && compress_solid);
|
||||
|
||||
if (srcImage->getPixelSizeInBits() <= 16) {
|
||||
SG_LOG(SG_IO, SG_INFO, "Ignoring " + absFileName + " for inclusion into the texture cache because pixel density too low at " << srcImage->getPixelSizeInBits() << " bits per pixek");
|
||||
can_compress = false;
|
||||
}
|
||||
|
||||
int height = srcImage->t();
|
||||
|
||||
// use the new file origin to determine any special processing
|
||||
@@ -386,6 +393,10 @@ ModelRegistry::readImage(const string& fileName,
|
||||
isEffect = true;
|
||||
// can_compress = false;
|
||||
}
|
||||
else if (sgoptC && !transparent && sgoptC->getLoadOriginHint() == SGReaderWriterOptions::LoadOriginHint::ORIGIN_CANVAS) {
|
||||
SG_LOG(SG_IO, SG_INFO, "From Canvas " + absFileName + " will generate mipmap only");
|
||||
can_compress = false;
|
||||
}
|
||||
if (can_compress)
|
||||
{
|
||||
std::string pot_message;
|
||||
|
||||
@@ -490,7 +490,8 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalParticleCallback::getEnabled()){//dbOptions->getPluginStringData("SimGear::PARTICLESYSTEM") != "OFF") {
|
||||
auto particlesManager = ParticlesGlobalManager::instance();
|
||||
if (particlesManager->isEnabled()) { //dbOptions->getPluginStringData("SimGear::PARTICLESYSTEM") != "OFF") {
|
||||
std::vector<SGPropertyNode_ptr> particle_nodes;
|
||||
particle_nodes = props->getChildren("particlesystem");
|
||||
for (unsigned i = 0; i < particle_nodes.size(); ++i) {
|
||||
@@ -503,9 +504,9 @@ sgLoad3DModel_internal(const SGPath& path,
|
||||
|
||||
options2->setDatabasePath(texturepath.utf8Str());
|
||||
}
|
||||
group->addChild(Particles::appendParticles(particle_nodes[i],
|
||||
prop_root,
|
||||
options2.get()));
|
||||
group->addChild(particlesManager->appendParticles(particle_nodes[i],
|
||||
prop_root,
|
||||
options2.get()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ void SGText::UpdateCallback::operator()(osg::Node * node, osg::NodeVisitor *nv )
|
||||
// be lazy and set the text only if the property has changed.
|
||||
// update() computes the glyph representation which looks
|
||||
// more expensive than a the above string compare.
|
||||
text->setText( buf );
|
||||
text->setText( buf, osgText::String::ENCODING_UTF8 );
|
||||
text->update();
|
||||
}
|
||||
traverse( node, nv );
|
||||
|
||||
@@ -1135,16 +1135,8 @@ void SpinAnimCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
double intPart;
|
||||
double rot = modf(rotation, &intPart);
|
||||
double angle = rot * 2.0 * osg::PI;
|
||||
const SGVec3d& sgcenter = transform->getCenter();
|
||||
const SGVec3d& sgaxis = transform->getAxis();
|
||||
Matrixd mat = Matrixd::translate(-sgcenter[0], -sgcenter[1], -sgcenter[2])
|
||||
* Matrixd::rotate(angle, sgaxis[0], sgaxis[1], sgaxis[2])
|
||||
* Matrixd::translate(sgcenter[0], sgcenter[1], sgcenter[2])
|
||||
* *cv->getModelViewMatrix();
|
||||
ref_ptr<RefMatrix> refmat = new RefMatrix(mat);
|
||||
cv->pushModelViewMatrix(refmat.get(), transform->getReferenceFrame());
|
||||
transform->setAngleRad(angle);
|
||||
traverse(transform, nv);
|
||||
cv->popModelViewMatrix();
|
||||
} else {
|
||||
traverse(transform, nv);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include "particles.hxx"
|
||||
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
@@ -33,62 +36,306 @@
|
||||
#include <osgParticle/MultiSegmentPlacer>
|
||||
#include <osgParticle/SectorPlacer>
|
||||
#include <osgParticle/ConstantRateCounter>
|
||||
#include <osgParticle/ParticleSystemUpdater>
|
||||
#include <osgParticle/ParticleSystem>
|
||||
#include <osgParticle/FluidProgram>
|
||||
|
||||
#include <osgUtil/CullVisitor>
|
||||
#include <osg/Geode>
|
||||
#include <osg/Group>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Node>
|
||||
|
||||
#include "particles.hxx"
|
||||
|
||||
#include <simgear/scene/model/animation.hxx>
|
||||
|
||||
using ParticleSystemRef = osg::ref_ptr<osgParticle::ParticleSystem>;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
void GlobalParticleCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
enabled = !enabledNode || enabledNode->getBoolValue();
|
||||
if (!enabled)
|
||||
return;
|
||||
SGQuatd q
|
||||
= SGQuatd::fromLonLatDeg(modelRoot->getFloatValue("/position/longitude-deg",0),
|
||||
modelRoot->getFloatValue("/position/latitude-deg",0));
|
||||
osg::Matrix om(toOsg(q));
|
||||
osg::Vec3 v(0,0,9.81);
|
||||
gravity = om.preMult(v);
|
||||
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
|
||||
const osg::Vec3& zUpWind = Particles::getWindVector();
|
||||
osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
|
||||
wind = om.preMult(w);
|
||||
|
||||
// SG_LOG(SG_PARTICLES, SG_ALERT,
|
||||
// "wind vector:" << w[0] << "," <<w[1] << "," << w[2]);
|
||||
class ParticlesGlobalManager::ParticlesGlobalManagerPrivate : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
ParticlesGlobalManagerPrivate();
|
||||
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
|
||||
|
||||
// only call this with the lock held!
|
||||
osg::Group* internalGetCommonRoot();
|
||||
|
||||
void updateParticleSystemsFromCullCallback(int currentFrameNumber, osg::NodeVisitor* nv);
|
||||
|
||||
void addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame);
|
||||
|
||||
void registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps);
|
||||
void registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame);
|
||||
|
||||
std::mutex _lock;
|
||||
bool _frozen = false;
|
||||
double _simulationDt = 0.0;
|
||||
osg::ref_ptr<osg::Group> _commonRoot;
|
||||
osg::ref_ptr<osg::Geode> _commonGeode;
|
||||
osg::Vec3 _wind;
|
||||
bool _enabled = true;
|
||||
osg::Vec3 _gravity;
|
||||
// osg::Vec3 _localWind;
|
||||
SGGeod _currentPosition;
|
||||
|
||||
SGConstPropertyNode_ptr _enabledNode;
|
||||
|
||||
using ParticleSystemWeakRef = osg::observer_ptr<osgParticle::ParticleSystem>;
|
||||
using ParticleSystemsWeakRefVec = std::vector<ParticleSystemWeakRef>;
|
||||
|
||||
using ParticleSystemsStrongRefVec = std::vector<ParticleSystemRef>;
|
||||
|
||||
ParticleSystemsWeakRefVec _systems;
|
||||
osg::ref_ptr<ParticlesGlobalManager::UpdaterCallback> _cullCallback;
|
||||
|
||||
using GroupRefVec = std::vector<osg::ref_ptr<osg::Group>>;
|
||||
GroupRefVec _newWorldParticles;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief this class replaces the need to use osgParticle::ParticleSystemUpdater, which has some
|
||||
thread-safety and ownership complications in our us case
|
||||
*/
|
||||
class ParticlesGlobalManager::UpdaterCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
UpdaterCallback(ParticlesGlobalManagerPrivate* p) : _manager(p)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
|
||||
{
|
||||
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
|
||||
if (cv && nv->getFrameStamp()) {
|
||||
if (_frameNumber < nv->getFrameStamp()->getFrameNumber()) {
|
||||
_frameNumber = nv->getFrameStamp()->getFrameNumber();
|
||||
_manager->updateParticleSystemsFromCullCallback(_frameNumber, nv);
|
||||
}
|
||||
}
|
||||
|
||||
// note, callback is responsible for scenegraph traversal so
|
||||
// they must call traverse(node,nv) to ensure that the
|
||||
// scene graph subtree (and associated callbacks) are traversed.
|
||||
traverse(node, nv);
|
||||
}
|
||||
|
||||
unsigned int _frameNumber = 0;
|
||||
ParticlesGlobalManagerPrivate* _manager;
|
||||
};
|
||||
|
||||
/**
|
||||
single-shot node callback, used to register a particle system with the global manager. Once run, removes
|
||||
itself. We used this to avoid updating a particle system until the load is complete and merged into the
|
||||
main scene.
|
||||
*/
|
||||
class ParticlesGlobalManager::RegistrationCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
|
||||
{
|
||||
auto d = ParticlesGlobalManager::instance()->d;
|
||||
d->addParticleSystem(_system, _frame);
|
||||
node->removeUpdateCallback(this); // suicide
|
||||
}
|
||||
|
||||
ParticleSystemRef _system;
|
||||
osg::ref_ptr<osg::Group> _frame;
|
||||
};
|
||||
|
||||
ParticlesGlobalManager::ParticlesGlobalManagerPrivate::ParticlesGlobalManagerPrivate() : _commonGeode(new osg::Geode),
|
||||
_cullCallback(new UpdaterCallback(this))
|
||||
{
|
||||
// callbacks are registered in initFromMainThread : depending on timing,
|
||||
// this constructor might be called from an osgDB thread
|
||||
}
|
||||
|
||||
|
||||
//static members
|
||||
osg::Vec3 GlobalParticleCallback::gravity;
|
||||
osg::Vec3 GlobalParticleCallback::wind;
|
||||
bool GlobalParticleCallback::enabled = true;
|
||||
SGConstPropertyNode_ptr GlobalParticleCallback::enabledNode = 0;
|
||||
|
||||
osg::ref_ptr<osg::Group> Particles::commonRoot;
|
||||
osg::ref_ptr<osgParticle::ParticleSystemUpdater> Particles::psu = new osgParticle::ParticleSystemUpdater;
|
||||
osg::ref_ptr<osg::Geode> Particles::commonGeode = new osg::Geode;
|
||||
osg::Vec3 Particles::_wind;
|
||||
bool Particles::_frozen = false;
|
||||
|
||||
Particles::Particles() :
|
||||
useGravity(false),
|
||||
useWind(false)
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
_systems.push_back(ps);
|
||||
|
||||
// we're inside an update callback here, so better not modify the
|
||||
// scene structure; defer it to later
|
||||
if (frame.get()) {
|
||||
_newWorldParticles.push_back(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps)
|
||||
{
|
||||
auto cb = new RegistrationCallback;
|
||||
cb->_system = ps;
|
||||
node->addUpdateCallback(cb);
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame)
|
||||
{
|
||||
auto cb = new RegistrationCallback;
|
||||
cb->_system = ps;
|
||||
cb->_frame = frame;
|
||||
node->addUpdateCallback(cb);
|
||||
}
|
||||
|
||||
// this is called from the main thread during scenery init
|
||||
// after this, we beign updarting particle systems
|
||||
void ParticlesGlobalManager::initFromMainThread()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->internalGetCommonRoot();
|
||||
d->_commonRoot->addUpdateCallback(d.get());
|
||||
d->_commonRoot->setCullingActive(false);
|
||||
d->_commonRoot->addCullCallback(d->_cullCallback);
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::update(double dt, const SGGeod& pos)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_simulationDt = dt;
|
||||
d->_currentPosition = pos;
|
||||
|
||||
for (auto f : d->_newWorldParticles) {
|
||||
d->internalGetCommonRoot()->addChild(f);
|
||||
}
|
||||
|
||||
d->_newWorldParticles.clear();
|
||||
}
|
||||
|
||||
// this is called from the main thread, since it's an update callback
|
||||
// lock any state used by updateParticleSystemsFromCullCallback, which
|
||||
// runs during culling, potentialy on a different thread
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
_enabled = !_enabledNode || _enabledNode->getBoolValue();
|
||||
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
const auto q = SGQuatd::fromLonLat(_currentPosition);
|
||||
osg::Matrix om(toOsg(q));
|
||||
osg::Vec3 v(0, 0, 9.81);
|
||||
_gravity = om.preMult(v);
|
||||
|
||||
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
|
||||
// const osg::Vec3& zUpWind = _wind;
|
||||
// osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
|
||||
// _localWind = om.preMult(w);
|
||||
|
||||
// while we have the lock, remove all expired systems
|
||||
auto firstInvalid = std::remove_if(_systems.begin(), _systems.end(), [](const ParticleSystemWeakRef& weak) {
|
||||
return !weak.valid();
|
||||
});
|
||||
_systems.erase(firstInvalid, _systems.end());
|
||||
}
|
||||
|
||||
// only call this with the lock held!
|
||||
osg::Group* ParticlesGlobalManager::ParticlesGlobalManagerPrivate::internalGetCommonRoot()
|
||||
{
|
||||
if (!_commonRoot.valid()) {
|
||||
_commonRoot = new osg::Group;
|
||||
_commonRoot->setName("common particle system root");
|
||||
_commonGeode->setName("common particle system geode");
|
||||
_commonRoot->addChild(_commonGeode);
|
||||
_commonRoot->setNodeMask(~simgear::MODELLIGHT_BIT);
|
||||
}
|
||||
return _commonRoot.get();
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::updateParticleSystemsFromCullCallback(int frameNumber, osg::NodeVisitor* nv)
|
||||
{
|
||||
ParticleSystemsStrongRefVec activeSystems;
|
||||
double dt = 0.0;
|
||||
|
||||
// begin locked section
|
||||
{
|
||||
std::lock_guard<std::mutex> g(_lock);
|
||||
activeSystems.reserve(_systems.size());
|
||||
for (const auto& psref : _systems) {
|
||||
ParticleSystemRef owningRef;
|
||||
if (!psref.lock(owningRef)) {
|
||||
// pointed to system is gone, skip it
|
||||
// we will clean these up in the update callback, don't
|
||||
// worry about that here
|
||||
continue;
|
||||
}
|
||||
|
||||
// add to the list we will update now
|
||||
activeSystems.push_back(owningRef);
|
||||
}
|
||||
|
||||
dt = _simulationDt;
|
||||
} // of locked section
|
||||
|
||||
// from here on, don't access class data; copy it all to local variables
|
||||
// before this line. this is important so we're not holding _lock during
|
||||
// culling, which might block osgDB threads for example.
|
||||
|
||||
if (dt <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& ps : activeSystems) {
|
||||
// code inside here is copied from osgParticle::ParticleSystemUpdate
|
||||
osgParticle::ParticleSystem::ScopedWriteLock lock(*(ps->getReadWriteMutex()));
|
||||
|
||||
// We need to allow at least 2 frames difference, because the particle system's lastFrameNumber
|
||||
// is updated in the draw thread which may not have completed yet.
|
||||
if (!ps->isFrozen() &&
|
||||
(!ps->getFreezeOnCull() || ((frameNumber - ps->getLastFrameNumber()) <= 2))) {
|
||||
ps->update(dt, *nv);
|
||||
}
|
||||
// end of code copied from osgParticle::ParticleSystemUpdate
|
||||
}
|
||||
}
|
||||
|
||||
static std::mutex static_managerLock;
|
||||
static std::unique_ptr<ParticlesGlobalManager> static_instance;
|
||||
|
||||
ParticlesGlobalManager* ParticlesGlobalManager::instance()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(static_managerLock);
|
||||
if (!static_instance) {
|
||||
static_instance.reset(new ParticlesGlobalManager);
|
||||
}
|
||||
|
||||
return static_instance.get();
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(static_managerLock);
|
||||
static_instance.reset();
|
||||
}
|
||||
|
||||
ParticlesGlobalManager::ParticlesGlobalManager() : d(new ParticlesGlobalManagerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
bool ParticlesGlobalManager::isEnabled() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->_enabled;
|
||||
}
|
||||
|
||||
bool ParticlesGlobalManager::isFrozen() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->_frozen;
|
||||
}
|
||||
|
||||
osg::Vec3 ParticlesGlobalManager::getWindVector() const
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->_wind;
|
||||
}
|
||||
|
||||
template <typename Object>
|
||||
class PointerGuard{
|
||||
public:
|
||||
PointerGuard() : _ptr(0) {}
|
||||
Object* get() { return _ptr; }
|
||||
Object* operator () ()
|
||||
{
|
||||
@@ -97,24 +344,9 @@ public:
|
||||
return _ptr;
|
||||
}
|
||||
private:
|
||||
Object* _ptr;
|
||||
Object* _ptr = nullptr;
|
||||
};
|
||||
|
||||
osg::Group* Particles::getCommonRoot()
|
||||
{
|
||||
if(!commonRoot.valid())
|
||||
{
|
||||
SG_LOG(SG_PARTICLES, SG_DEBUG, "Particle common root called.");
|
||||
commonRoot = new osg::Group;
|
||||
commonRoot.get()->setName("common particle system root");
|
||||
commonGeode.get()->setName("common particle system geode");
|
||||
commonRoot.get()->addChild(commonGeode.get());
|
||||
commonRoot.get()->addChild(psu.get());
|
||||
commonRoot->setNodeMask( ~simgear::MODELLIGHT_BIT );
|
||||
}
|
||||
return commonRoot.get();
|
||||
}
|
||||
|
||||
void transformParticles(osgParticle::ParticleSystem* particleSys,
|
||||
const osg::Matrix& mat)
|
||||
{
|
||||
@@ -129,10 +361,182 @@ void transformParticles(osgParticle::ParticleSystem* particleSys,
|
||||
}
|
||||
}
|
||||
|
||||
osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot,
|
||||
const osgDB::Options*
|
||||
options)
|
||||
void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
auto globalManager = ParticlesGlobalManager::instance();
|
||||
|
||||
//SG_LOG(SG_PARTICLES, SG_ALERT, "callback!\n");
|
||||
particleSys->setFrozen(globalManager->isFrozen());
|
||||
|
||||
using namespace osg;
|
||||
if (shooterValue)
|
||||
shooter->setInitialSpeedRange(shooterValue->getValue(),
|
||||
(shooterValue->getValue() + shooterExtraRange));
|
||||
if (counterValue)
|
||||
counter->setRateRange(counterValue->getValue(),
|
||||
counterValue->getValue() + counterExtraRange);
|
||||
else if (counterCond)
|
||||
counter->setRateRange(counterStaticValue,
|
||||
counterStaticValue + counterStaticExtraRange);
|
||||
if (!globalManager->isEnabled() || (counterCond && !counterCond->test()))
|
||||
counter->setRateRange(0, 0);
|
||||
bool colorchange = false;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (colorComponents[i]) {
|
||||
staticColorComponents[i] = colorComponents[i]->getValue();
|
||||
colorchange = true;
|
||||
}
|
||||
}
|
||||
if (colorchange)
|
||||
particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
|
||||
if (startSizeValue)
|
||||
startSize = startSizeValue->getValue();
|
||||
if (endSizeValue)
|
||||
endSize = endSizeValue->getValue();
|
||||
if (startSizeValue || endSizeValue)
|
||||
particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
|
||||
if (lifeValue)
|
||||
particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
|
||||
|
||||
if (particleFrame.valid()) {
|
||||
MatrixList mlist = node->getWorldMatrices();
|
||||
if (!mlist.empty()) {
|
||||
const Matrix& particleMat = particleFrame->getMatrix();
|
||||
Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
|
||||
Vec3d displace = emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
|
||||
particleMat(3, 2));
|
||||
if (displace * displace > 10000.0 * 10000.0) {
|
||||
// Make new frame for particle system, coincident with
|
||||
// the emitter frame, but oriented with local Z.
|
||||
SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
|
||||
Matrix newParticleMat = makeZUpFrame(geod);
|
||||
Matrix changeParticleFrame = particleMat * Matrix::inverse(newParticleMat);
|
||||
particleFrame->setMatrix(newParticleMat);
|
||||
transformParticles(particleSys.get(), changeParticleFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (program.valid() && useWind)
|
||||
program->setWind(globalManager->getWindVector());
|
||||
}
|
||||
|
||||
void Particles::setupShooterSpeedData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
shooterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!shooterValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: shooter property error!\n");
|
||||
}
|
||||
shooterExtraRange = configNode->getFloatValue("extrarange", 0);
|
||||
}
|
||||
|
||||
void Particles::setupCounterData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!counterValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "counter property error!\n");
|
||||
}
|
||||
counterExtraRange = configNode->getFloatValue("extrarange", 0);
|
||||
}
|
||||
|
||||
void Particles::Particles::setupCounterCondition(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterCond = sgReadCondition(modelRoot, configNode);
|
||||
}
|
||||
|
||||
void Particles::setupCounterCondition(float aCounterStaticValue,
|
||||
float aCounterStaticExtraRange)
|
||||
{
|
||||
counterStaticValue = aCounterStaticValue;
|
||||
counterStaticExtraRange = aCounterStaticExtraRange;
|
||||
}
|
||||
|
||||
void Particles::setupStartSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
startSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!startSizeValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Particles::setupEndSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
endSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!endSizeValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Particles::setupLifeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
lifeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if (!lifeValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: lifeValue error!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Particles::setupColorComponent(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot, int color,
|
||||
int component)
|
||||
{
|
||||
SGSharedPtr<SGExpressiond> colorValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(),
|
||||
SGLimitsd::max());
|
||||
if (!colorValue) {
|
||||
SG_LOG(SG_GENERAL, SG_DEV_WARN, "Particles: color property error!\n");
|
||||
}
|
||||
colorComponents[(color * 4) + component] = colorValue;
|
||||
//number of color components = 4
|
||||
}
|
||||
|
||||
void Particles::setupStaticColorComponent(float r1, float g1, float b1, float a1,
|
||||
float r2, float g2, float b2, float a2)
|
||||
{
|
||||
staticColorComponents[0] = r1;
|
||||
staticColorComponents[1] = g1;
|
||||
staticColorComponents[2] = b1;
|
||||
staticColorComponents[3] = a1;
|
||||
staticColorComponents[4] = r2;
|
||||
staticColorComponents[5] = g2;
|
||||
staticColorComponents[6] = b2;
|
||||
staticColorComponents[7] = a2;
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::setWindVector(const osg::Vec3& wind)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_wind = wind;
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::setWindFrom(const double from_deg, const double speed_kt)
|
||||
{
|
||||
double map_rad = -from_deg * SG_DEGREES_TO_RADIANS;
|
||||
double speed_mps = speed_kt * SG_KT_TO_MPS;
|
||||
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_wind[0] = cos(map_rad) * speed_mps;
|
||||
d->_wind[1] = sin(map_rad) * speed_mps;
|
||||
d->_wind[2] = 0.0;
|
||||
}
|
||||
|
||||
|
||||
osg::Group* ParticlesGlobalManager::getCommonRoot()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
return d->internalGetCommonRoot();
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Group> ParticlesGlobalManager::appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options)
|
||||
{
|
||||
SG_LOG(SG_PARTICLES, SG_DEBUG,
|
||||
"Setting up a particle system." << std::boolalpha
|
||||
@@ -152,7 +556,7 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
<< "\n Wind: " << configNode->getChild("program")->getBoolValue("wind", true)
|
||||
<< std::noboolalpha);
|
||||
|
||||
osgParticle::ParticleSystem *particleSys;
|
||||
osg::ref_ptr<osgParticle::ParticleSystem> particleSys;
|
||||
|
||||
//create a generic particle system
|
||||
std::string type = configNode->getStringValue("type", "normal");
|
||||
@@ -160,11 +564,10 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
particleSys = new osgParticle::ParticleSystem;
|
||||
else
|
||||
particleSys = new osgParticle::ConnectedParticleSystem;
|
||||
|
||||
//may not be used depending on the configuration
|
||||
PointerGuard<Particles> callback;
|
||||
|
||||
getPSU()->addParticleSystem(particleSys);
|
||||
getPSU()->setUpdateCallback(new GlobalParticleCallback(modelRoot));
|
||||
//contains counter, placer and shooter by default
|
||||
osgParticle::ModularEmitter* emitter = new osgParticle::ModularEmitter;
|
||||
|
||||
@@ -172,7 +575,7 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
|
||||
// Set up the alignment node ("stolen" from animation.cxx)
|
||||
// XXX Order of rotations is probably not correct.
|
||||
osg::MatrixTransform *align = new osg::MatrixTransform;
|
||||
osg::ref_ptr<osg::MatrixTransform> align = new osg::MatrixTransform;
|
||||
osg::Matrix res_matrix;
|
||||
res_matrix.makeRotate(
|
||||
configNode->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
|
||||
@@ -187,12 +590,8 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
configNode->getFloatValue("offsets/y-m", 0.0),
|
||||
configNode->getFloatValue("offsets/z-m", 0.0));
|
||||
align->setMatrix(res_matrix * tmat);
|
||||
|
||||
align->setName("particle align");
|
||||
|
||||
//if (dynamic_cast<CustomModularEmitter*>(emitter)==0) SG_LOG(SG_PARTICLES, SG_ALERT, "observer error\n");
|
||||
//align->addObserver(dynamic_cast<CustomModularEmitter*>(emitter));
|
||||
|
||||
align->addChild(emitter);
|
||||
|
||||
//this name can be used in the XML animation as if it was a submodel
|
||||
@@ -209,7 +608,6 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
osg::Geode* g = new osg::Geode;
|
||||
g->addDrawable(particleSys);
|
||||
callback()->particleFrame->addChild(g);
|
||||
getCommonRoot()->addChild(callback()->particleFrame.get());
|
||||
}
|
||||
std::string textureFile;
|
||||
if (configNode->hasValue("texture")) {
|
||||
@@ -505,66 +903,25 @@ osg::Group * Particles::appendParticles(const SGPropertyNode* configNode,
|
||||
emitter->setUpdateCallback(callback.get());
|
||||
}
|
||||
|
||||
if (attach == "local") {
|
||||
d->registerNewLocalParticleSystem(align, particleSys);
|
||||
} else {
|
||||
d->registerNewWorldParticleSystem(align, particleSys, callback()->particleFrame);
|
||||
}
|
||||
|
||||
return align;
|
||||
}
|
||||
|
||||
void Particles::operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
void ParticlesGlobalManager::setSwitchNode(const SGPropertyNode* n)
|
||||
{
|
||||
//SG_LOG(SG_PARTICLES, SG_ALERT, "callback!\n");
|
||||
this->particleSys->setFrozen(_frozen);
|
||||
|
||||
using namespace osg;
|
||||
if (shooterValue)
|
||||
shooter->setInitialSpeedRange(shooterValue->getValue(),
|
||||
(shooterValue->getValue()
|
||||
+ shooterExtraRange));
|
||||
if (counterValue)
|
||||
counter->setRateRange(counterValue->getValue(),
|
||||
counterValue->getValue() + counterExtraRange);
|
||||
else if (counterCond)
|
||||
counter->setRateRange(counterStaticValue,
|
||||
counterStaticValue + counterStaticExtraRange);
|
||||
if (!GlobalParticleCallback::getEnabled() || (counterCond && !counterCond->test()))
|
||||
counter->setRateRange(0, 0);
|
||||
bool colorchange=false;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (colorComponents[i]) {
|
||||
staticColorComponents[i] = colorComponents[i]->getValue();
|
||||
colorchange=true;
|
||||
}
|
||||
}
|
||||
if (colorchange)
|
||||
particleSys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4( Vec4(staticColorComponents[0], staticColorComponents[1], staticColorComponents[2], staticColorComponents[3]), Vec4(staticColorComponents[4], staticColorComponents[5], staticColorComponents[6], staticColorComponents[7])));
|
||||
if (startSizeValue)
|
||||
startSize = startSizeValue->getValue();
|
||||
if (endSizeValue)
|
||||
endSize = endSizeValue->getValue();
|
||||
if (startSizeValue || endSizeValue)
|
||||
particleSys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(startSize, endSize));
|
||||
if (lifeValue)
|
||||
particleSys->getDefaultParticleTemplate().setLifeTime(lifeValue->getValue());
|
||||
|
||||
if (particleFrame.valid()) {
|
||||
MatrixList mlist = node->getWorldMatrices();
|
||||
if (!mlist.empty()) {
|
||||
const Matrix& particleMat = particleFrame->getMatrix();
|
||||
Vec3d emitOrigin(mlist[0](3, 0), mlist[0](3, 1), mlist[0](3, 2));
|
||||
Vec3d displace
|
||||
= emitOrigin - Vec3d(particleMat(3, 0), particleMat(3, 1),
|
||||
particleMat(3, 2));
|
||||
if (displace * displace > 10000.0 * 10000.0) {
|
||||
// Make new frame for particle system, coincident with
|
||||
// the emitter frame, but oriented with local Z.
|
||||
SGGeod geod = SGGeod::fromCart(toSG(emitOrigin));
|
||||
Matrix newParticleMat = makeZUpFrame(geod);
|
||||
Matrix changeParticleFrame
|
||||
= particleMat * Matrix::inverse(newParticleMat);
|
||||
particleFrame->setMatrix(newParticleMat);
|
||||
transformParticles(particleSys.get(), changeParticleFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (program.valid() && useWind)
|
||||
program->setWind(_wind);
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_enabledNode = n;
|
||||
}
|
||||
|
||||
void ParticlesGlobalManager::setFrozen(bool b)
|
||||
{
|
||||
std::lock_guard<std::mutex> g(d->_lock);
|
||||
d->_frozen = b;
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// particles.hxx - classes to manage particles
|
||||
// Copyright (C) 2008 Tiago Gusm<73>o
|
||||
// Copyright (C) 2008 Tiago Gusm<73>o
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
@@ -41,7 +41,6 @@ class NodeVisitor;
|
||||
namespace osgParticle
|
||||
{
|
||||
class ParticleSystem;
|
||||
class ParticleSystemUpdater;
|
||||
}
|
||||
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
@@ -53,133 +52,37 @@ class ParticleSystemUpdater;
|
||||
#include <simgear/math/SGMatrix.hxx>
|
||||
|
||||
|
||||
// Has anyone done anything *really* stupid, like making min and max macros?
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#include "animation.hxx"
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class GlobalParticleCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
GlobalParticleCallback(const SGPropertyNode* modelRoot)
|
||||
{
|
||||
this->modelRoot=modelRoot;
|
||||
}
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||
|
||||
static const osg::Vec3 &getGravityVector()
|
||||
{
|
||||
return gravity;
|
||||
}
|
||||
|
||||
static const osg::Vec3 &getWindVector()
|
||||
{
|
||||
return wind;
|
||||
}
|
||||
|
||||
static void setSwitch(const SGPropertyNode* n)
|
||||
{
|
||||
enabledNode = n;
|
||||
}
|
||||
|
||||
static bool getEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static osg::Vec3 gravity;
|
||||
static osg::Vec3 wind;
|
||||
SGConstPropertyNode_ptr modelRoot;
|
||||
static SGConstPropertyNode_ptr enabledNode;
|
||||
static bool enabled;
|
||||
};
|
||||
|
||||
|
||||
class ParticlesGlobalManager;
|
||||
|
||||
class Particles : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
Particles();
|
||||
Particles() = default;
|
||||
|
||||
static osg::Group * appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
|
||||
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
|
||||
|
||||
void setupShooterSpeedData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
shooterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!shooterValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "shooter property error!\n");
|
||||
}
|
||||
shooterExtraRange = configNode->getFloatValue("extrarange",0);
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupCounterData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!counterValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "counter property error!\n");
|
||||
}
|
||||
counterExtraRange = configNode->getFloatValue("extrarange",0);
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupCounterCondition(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
counterCond = sgReadCondition(modelRoot, configNode);
|
||||
}
|
||||
|
||||
SGPropertyNode* modelRoot);
|
||||
void setupCounterCondition(float counterStaticValue,
|
||||
float counterStaticExtraRange)
|
||||
{
|
||||
this->counterStaticValue = counterStaticValue;
|
||||
this->counterStaticExtraRange = counterStaticExtraRange;
|
||||
}
|
||||
float counterStaticExtraRange);
|
||||
|
||||
void setupStartSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
startSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!startSizeValue)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupEndSizeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
endSizeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!endSizeValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "startSizeValue error!\n");
|
||||
}
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupLifeData(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot)
|
||||
{
|
||||
lifeValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(), SGLimitsd::max());
|
||||
if(!lifeValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "lifeValue error!\n");
|
||||
}
|
||||
}
|
||||
SGPropertyNode* modelRoot);
|
||||
|
||||
void setupStaticSizeData(float startSize, float endSize)
|
||||
{
|
||||
@@ -212,60 +115,14 @@ public:
|
||||
//component: r=0, g=1, ... ; color: 0=start color, 1=end color
|
||||
void setupColorComponent(const SGPropertyNode* configNode,
|
||||
SGPropertyNode* modelRoot, int color,
|
||||
int component)
|
||||
{
|
||||
SGExpressiond *colorValue = read_value(configNode, modelRoot, "-m",
|
||||
-SGLimitsd::max(),
|
||||
SGLimitsd::max());
|
||||
if(!colorValue){
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "color property error!\n");
|
||||
}
|
||||
colorComponents[(color*4)+component] = colorValue;
|
||||
//number of color components = 4
|
||||
}
|
||||
int component);
|
||||
|
||||
void setupStaticColorComponent(float r1,float g1, float b1, float a1,
|
||||
float r2, float g2, float b2, float a2)
|
||||
{
|
||||
staticColorComponents[0] = r1;
|
||||
staticColorComponents[1] = g1;
|
||||
staticColorComponents[2] = b1;
|
||||
staticColorComponents[3] = a1;
|
||||
staticColorComponents[4] = r2;
|
||||
staticColorComponents[5] = g2;
|
||||
staticColorComponents[6] = b2;
|
||||
staticColorComponents[7] = a2;
|
||||
}
|
||||
void setupStaticColorComponent(float r1, float g1, float b1, float a1,
|
||||
float r2, float g2, float b2, float a2);
|
||||
|
||||
static osg::Group * getCommonRoot();
|
||||
|
||||
static osg::Geode * getCommonGeode()
|
||||
{
|
||||
return commonGeode.get();
|
||||
}
|
||||
|
||||
static osgParticle::ParticleSystemUpdater * getPSU()
|
||||
{
|
||||
return psu.get();
|
||||
}
|
||||
|
||||
static void setFrozen(bool e) { _frozen = e; }
|
||||
|
||||
/**
|
||||
* Set and get the wind vector for particles in the
|
||||
* atmosphere. This vector is in the Z-up Y-north frame, and the
|
||||
* magnitude is the velocity in meters per second.
|
||||
*/
|
||||
static void setWindVector(const osg::Vec3& wind) { _wind = wind; }
|
||||
static void setWindFrom(const double from_deg, const double speed_kt) {
|
||||
double map_rad = -from_deg * SG_DEGREES_TO_RADIANS;
|
||||
double speed_mps = speed_kt * SG_KT_TO_MPS;
|
||||
_wind[0] = cos(map_rad) * speed_mps;
|
||||
_wind[1] = sin(map_rad) * speed_mps;
|
||||
_wind[2] = 0.0;
|
||||
}
|
||||
static const osg::Vec3& getWindVector() { return _wind; }
|
||||
protected:
|
||||
friend class ParticlesGlobalManager;
|
||||
|
||||
float shooterExtraRange;
|
||||
float counterExtraRange;
|
||||
SGSharedPtr<SGExpressiond> shooterValue;
|
||||
@@ -285,39 +142,60 @@ protected:
|
||||
osg::ref_ptr<osgParticle::ParticleSystem> particleSys;
|
||||
osg::ref_ptr<osgParticle::FluidProgram> program;
|
||||
osg::ref_ptr<osg::MatrixTransform> particleFrame;
|
||||
|
||||
bool useGravity;
|
||||
bool useWind;
|
||||
static bool _frozen;
|
||||
static osg::ref_ptr<osgParticle::ParticleSystemUpdater> psu;
|
||||
static osg::ref_ptr<osg::Group> commonRoot;
|
||||
static osg::ref_ptr<osg::Geode> commonGeode;
|
||||
static osg::Vec3 _wind;
|
||||
|
||||
bool useGravity = false;
|
||||
bool useWind = false;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
class CustomModularEmitter : public osgParticle::ModularEmitter, public osg::Observer
|
||||
class ParticlesGlobalManager
|
||||
{
|
||||
public:
|
||||
~ParticlesGlobalManager() = default;
|
||||
|
||||
virtual void objectDeleted (void *)
|
||||
{
|
||||
SG_LOG(SG_GENERAL, SG_ALERT, "object deleted!\n");
|
||||
static ParticlesGlobalManager* instance();
|
||||
static void clear();
|
||||
|
||||
SGParticles::getCommonRoot()->removeChild(this->getParticleSystem()->getParent(0));
|
||||
SGParticles::getPSU()->removeParticleSystem(this->getParticleSystem());
|
||||
}
|
||||
bool isEnabled() const;
|
||||
|
||||
osg::ref_ptr<osg::Group> appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
|
||||
|
||||
// ~CustomModularEmitter()
|
||||
// {
|
||||
// SG_LOG(SG_GENERAL, SG_ALERT, "object deleted!\n");
|
||||
//
|
||||
// SGParticles::getCommonRoot()->removeChild(this->getParticleSystem()->getParent(0));
|
||||
// SGParticles::getPSU()->removeParticleSystem(this->getParticleSystem());
|
||||
// }
|
||||
osg::Group* getCommonRoot();
|
||||
osg::Geode* getCommonGeode();
|
||||
|
||||
void initFromMainThread();
|
||||
|
||||
void setFrozen(bool e);
|
||||
bool isFrozen() const;
|
||||
|
||||
/**
|
||||
@brief update function: call from the main thread , outside of OSG calls.
|
||||
*/
|
||||
void update(double dt, const SGGeod& pos);
|
||||
|
||||
void setSwitchNode(const SGPropertyNode* n);
|
||||
|
||||
/**
|
||||
* Set and get the wind vector for particles in the
|
||||
* atmosphere. This vector is in the Z-up Y-north frame, and the
|
||||
* magnitude is the velocity in meters per second.
|
||||
*/
|
||||
void setWindVector(const osg::Vec3& wind);
|
||||
void setWindFrom(const double from_deg, const double speed_kt);
|
||||
|
||||
osg::Vec3 getWindVector() const;
|
||||
|
||||
private:
|
||||
ParticlesGlobalManager();
|
||||
|
||||
class ParticlesGlobalManagerPrivate;
|
||||
class UpdaterCallback;
|
||||
class RegistrationCallback;
|
||||
|
||||
// because Private inherits NodeCallback, we need to own it
|
||||
// via an osg::ref_ptr
|
||||
osg::ref_ptr<ParticlesGlobalManagerPrivate> d;
|
||||
};
|
||||
*/
|
||||
|
||||
} // namespace simgear
|
||||
|
||||
#endif
|
||||
|
||||
@@ -294,7 +294,7 @@ ReaderWriterSPT::createTree(const BucketBox& bucketBox, const LocalOptions& opti
|
||||
osg::ref_ptr<osg::Node>
|
||||
ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const
|
||||
{
|
||||
osg::PagedLOD* pagedLOD = new osg::PagedLOD;
|
||||
osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD;
|
||||
|
||||
pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
|
||||
SGSpheref sphere = bucketBox.getBoundingSphere();
|
||||
|
||||
@@ -212,7 +212,8 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
STGObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
|
||||
quadtree.buildQuadTree(_objectStaticList.begin(), _objectStaticList.end());
|
||||
osg::ref_ptr<osg::Group> group = quadtree.getRoot();
|
||||
group->setName("STG-group-A");
|
||||
string group_name = string("STG-group-A ").append(_bucket.gen_index_str());
|
||||
group->setName(group_name);
|
||||
group->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
simgear::AirportSignBuilder signBuilder(_options->getMaterialLib(), _bucket.get_center());
|
||||
@@ -221,29 +222,41 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
if (signBuilder.getSignsGroup())
|
||||
group->addChild(signBuilder.getSignsGroup());
|
||||
|
||||
if (_buildingList.size() > 0) {
|
||||
if (!_buildingList.empty()) {
|
||||
SGMaterialLibPtr matlib = _options->getMaterialLib();
|
||||
bool useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
|
||||
|
||||
if (!matlib) {
|
||||
SG_LOG( SG_TERRAIN, SG_ALERT, "Unable to get materials definition for buildings");
|
||||
} else {
|
||||
for (std::list<_BuildingList>::iterator i = _buildingList.begin(); i != _buildingList.end(); ++i) {
|
||||
for (const auto& b : _buildingList) {
|
||||
// Build buildings for each list of buildings
|
||||
SGGeod geodPos = SGGeod::fromDegM(i->_lon, i->_lat, 0.0);
|
||||
SGMaterial* mat = matlib->find(i->_material_name, geodPos);
|
||||
SGPath path = SGPath(i->_filename);
|
||||
SGGeod geodPos = SGGeod::fromDegM(b._lon, b._lat, 0.0);
|
||||
SGSharedPtr<SGMaterial> mat = matlib->find(b._material_name, geodPos);
|
||||
|
||||
// trying to avoid crash on null material, see:
|
||||
// https://sentry.io/organizations/flightgear/issues/1867075869
|
||||
if (!mat) {
|
||||
SG_LOG(SG_TERRAIN, SG_ALERT, "Building specifies unknown material: " << b._material_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto path = SGPath(b._filename);
|
||||
SGBuildingBin* buildingBin = new SGBuildingBin(path, mat, useVBOs);
|
||||
|
||||
SGBuildingBinList buildingBinList;
|
||||
buildingBinList.push_back(buildingBin);
|
||||
SGBuildingBinList bbList;
|
||||
bbList.push_back(buildingBin);
|
||||
|
||||
osg::MatrixTransform* matrixTransform;
|
||||
matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(i->_lon, i->_lat, i->_elev)));
|
||||
matrixTransform = new osg::MatrixTransform(makeZUpFrame(SGGeod::fromDegM(b._lon, b._lat, b._elev)));
|
||||
matrixTransform->setName("rotateBuildings");
|
||||
matrixTransform->setDataVariance(osg::Object::STATIC);
|
||||
matrixTransform->addChild(createRandomBuildings(buildingBinList, osg::Matrix::identity(), _options));
|
||||
matrixTransform->addChild(createRandomBuildings(bbList, osg::Matrix::identity(), _options));
|
||||
group->addChild(matrixTransform);
|
||||
|
||||
std::for_each(bbList.begin(), bbList.end(), [](SGBuildingBin* bb) {
|
||||
delete bb;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -574,10 +587,12 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
{
|
||||
osg::ref_ptr<SGReaderWriterOptions> options;
|
||||
options = SGReaderWriterOptions::copyOrCreate(opt);
|
||||
float pagedLODExpiry = atoi(options->getPluginStringData("SimGear::PAGED_LOD_EXPIRY").c_str());
|
||||
|
||||
osg::ref_ptr<osg::Group> terrainGroup = new osg::Group;
|
||||
terrainGroup->setDataVariance(osg::Object::STATIC);
|
||||
terrainGroup->setName("terrain");
|
||||
std::string terrain_name = string("terrain ").append(bucket.gen_index_str());
|
||||
terrainGroup->setName(terrain_name);
|
||||
|
||||
if (_foundBase) {
|
||||
for (auto stgObject : _objectList) {
|
||||
@@ -625,11 +640,13 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
} else {
|
||||
osg::PagedLOD* pagedLOD = new osg::PagedLOD;
|
||||
pagedLOD->setCenterMode(osg::PagedLOD::USE_BOUNDING_SPHERE_CENTER);
|
||||
pagedLOD->setName("pagedObjectLOD");
|
||||
std::string name = string("pagedObjectLOD ").append(bucket.gen_index_str());
|
||||
pagedLOD->setName(name);
|
||||
|
||||
// This should be visible in any case.
|
||||
// If this is replaced by some lower level of detail, the parent LOD node handles this.
|
||||
pagedLOD->addChild(terrainGroup, 0, std::numeric_limits<float>::max());
|
||||
pagedLOD->setMinimumExpiryTime(0, pagedLODExpiry);
|
||||
|
||||
// we just need to know about the read file callback that itself holds the data
|
||||
osg::ref_ptr<DelayLoadReadFileCallback> readFileCallback = new DelayLoadReadFileCallback;
|
||||
@@ -646,10 +663,11 @@ struct ReaderWriterSTG::_ModelBin {
|
||||
|
||||
// Objects may end up displayed up to 2x the object range.
|
||||
pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough);
|
||||
pagedLOD->setMinimumExpiryTime(pagedLOD->getNumChildren(), pagedLODExpiry);
|
||||
pagedLOD->setRadius(SG_TILE_RADIUS);
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough));
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Radius: " << SG_TILE_RADIUS);
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() );
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Range: " << (2.0 * _object_range_rough));
|
||||
SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile " << bucket.gen_index_str() << " PagedLOD Radius: " << SG_TILE_RADIUS);
|
||||
return pagedLOD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,6 +441,10 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
if (hash_pos != std::string::npos)
|
||||
line.resize(hash_pos);
|
||||
|
||||
if (line.empty()) {
|
||||
continue; // skip blank lines
|
||||
}
|
||||
|
||||
// and process further
|
||||
std::stringstream in(line);
|
||||
|
||||
@@ -462,9 +466,17 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
// F is the number of floors (integer)
|
||||
// WT is the texture index to use (integer) for walls. Buildings with the same WT value will have the same wall texture assigned. There are 6 small, 6 medium and 4 large textures.
|
||||
// RT is the texture index to use (integer) for roofs. Buildings with the same RT value will have the same roof texture assigned. There are 6 small, 6 medium and 4 large textures.
|
||||
float x, y, z, r, w, d, h, p;
|
||||
int b, s, o, f, wt, rt;
|
||||
in >> x >> y >> z >> r >> b >> w >> d >> h >> p >> s >> o >> f >> wt >> rt;
|
||||
float x = 0.0f, y = 0.0f, z = 0.0f, r = 0.0f, w = 0.0f, d = 0.0f, h = 0.0f, p = 0.0f;
|
||||
int b = 0, s = 0, o = 0, f = 0, wt = 0, rt = 0;
|
||||
in >> x >> y >> z >> r >> b;
|
||||
|
||||
if (in.bad() || in.fail()) {
|
||||
SG_LOG(SG_TERRAIN, SG_WARN, "Error parsing build entry in: " << absoluteFileName << " line: \"" << line << "\"");
|
||||
continue;
|
||||
}
|
||||
|
||||
// these might fail, so check them after we look at failbit
|
||||
in >> w >> d >> h >> p >> s >> o >> f >> wt >> rt;
|
||||
|
||||
//SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << b );
|
||||
SGVec3f loc = SGVec3f(x,y,z);
|
||||
@@ -489,14 +501,20 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
};
|
||||
|
||||
// Set up the building set based on the material definitions
|
||||
SGBuildingBin::SGBuildingBin(const SGMaterial *mat, bool useVBOs) {
|
||||
material_name = new std::string(mat->get_names()[0]);
|
||||
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building material " << material_name);
|
||||
material = mat;
|
||||
texture = new std::string(mat->get_building_texture());
|
||||
lightMap = new std::string(mat->get_building_lightmap());
|
||||
buildingRange = mat->get_building_range();
|
||||
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building texture " << texture);
|
||||
SGBuildingBin::SGBuildingBin(const SGMaterial* mat, bool useVBOs) : _material(const_cast<SGMaterial*>(mat))
|
||||
{
|
||||
const auto& materialNames = mat->get_names();
|
||||
if (materialNames.empty()) {
|
||||
SG_LOG(SG_TERRAIN, SG_DEV_ALERT, "SGBuildingBin: material has zero names defined");
|
||||
} else {
|
||||
_materialName = materialNames.front();
|
||||
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building material " << _materialName);
|
||||
}
|
||||
|
||||
_textureName = mat->get_building_texture();
|
||||
_lightMapName = mat->get_building_lightmap();
|
||||
buildingRange = mat->get_building_range();
|
||||
SG_LOG(SG_TERRAIN, SG_DEBUG, "Building texture " << _textureName);
|
||||
}
|
||||
|
||||
SGBuildingBin::~SGBuildingBin() {
|
||||
@@ -644,15 +662,15 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
if (buildingtype == SGBuildingBin::SMALL) {
|
||||
// Small building
|
||||
// Maximum number of floors is 3, and maximum width/depth is 192m.
|
||||
width = material->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_small_max_width() - material->get_building_small_min_width());
|
||||
depth = material->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_small_max_depth() - material->get_building_small_min_depth());
|
||||
floors = SGMisc<double>::round(material->get_building_small_min_floors() + mt_rand(&seed) * (material->get_building_small_max_floors() - material->get_building_small_min_floors()));
|
||||
width = _material->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_small_max_width() - _material->get_building_small_min_width());
|
||||
depth = _material->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_small_max_depth() - _material->get_building_small_min_depth());
|
||||
floors = SGMisc<double>::round(_material->get_building_small_min_floors() + mt_rand(&seed) * (_material->get_building_small_max_floors() - _material->get_building_small_min_floors()));
|
||||
height = floors * (2.8 + mt_rand(&seed));
|
||||
|
||||
// Small buildings are never deeper than they are wide.
|
||||
if (depth > width) { depth = width; }
|
||||
|
||||
pitch_height = (mt_rand(&seed) < material->get_building_small_pitch()) ? 3.0 : 0.0;
|
||||
pitch_height = (mt_rand(&seed) < _material->get_building_small_pitch()) ? 3.0 : 0.0;
|
||||
|
||||
if (pitch_height == 0.0) {
|
||||
roof_shape = 0;
|
||||
@@ -663,18 +681,18 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
}
|
||||
} else if (buildingtype == SGBuildingBin::MEDIUM) {
|
||||
// MEDIUM BUILDING
|
||||
width = material->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_medium_max_width() - material->get_building_medium_min_width());
|
||||
depth = material->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_medium_max_depth() - material->get_building_medium_min_depth());
|
||||
floors = SGMisc<double>::round(material->get_building_medium_min_floors() + mt_rand(&seed) * (material->get_building_medium_max_floors() - material->get_building_medium_min_floors()));
|
||||
width = _material->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_medium_max_width() - _material->get_building_medium_min_width());
|
||||
depth = _material->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (_material->get_building_medium_max_depth() - _material->get_building_medium_min_depth());
|
||||
floors = SGMisc<double>::round(_material->get_building_medium_min_floors() + mt_rand(&seed) * (_material->get_building_medium_max_floors() - _material->get_building_medium_min_floors()));
|
||||
height = floors * (2.8 + mt_rand(&seed));
|
||||
|
||||
while ((height > width) && (floors > material->get_building_medium_min_floors())) {
|
||||
// Ensure that medium buildings aren't taller than they are wide
|
||||
floors--;
|
||||
height = floors * (2.8 + mt_rand(&seed));
|
||||
while ((height > width) && (floors > _material->get_building_medium_min_floors())) {
|
||||
// Ensure that medium buildings aren't taller than they are wide
|
||||
floors--;
|
||||
height = floors * (2.8 + mt_rand(&seed));
|
||||
}
|
||||
|
||||
pitch_height = (mt_rand(&seed) < material->get_building_medium_pitch()) ? 3.0 : 0.0;
|
||||
pitch_height = (mt_rand(&seed) < _material->get_building_medium_pitch()) ? 3.0 : 0.0;
|
||||
|
||||
if (pitch_height == 0.0) {
|
||||
roof_shape = 0;
|
||||
@@ -685,11 +703,11 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
}
|
||||
} else {
|
||||
// LARGE BUILDING
|
||||
width = material->get_building_large_min_width() + mt_rand(&seed) * (material->get_building_large_max_width() - material->get_building_large_min_width());
|
||||
depth = material->get_building_large_min_depth() + mt_rand(&seed) * (material->get_building_large_max_depth() - material->get_building_large_min_depth());
|
||||
floors = SGMisc<double>::round(material->get_building_large_min_floors() + mt_rand(&seed) * (material->get_building_large_max_floors() - material->get_building_large_min_floors()));
|
||||
width = _material->get_building_large_min_width() + mt_rand(&seed) * (_material->get_building_large_max_width() - _material->get_building_large_min_width());
|
||||
depth = _material->get_building_large_min_depth() + mt_rand(&seed) * (_material->get_building_large_max_depth() - _material->get_building_large_min_depth());
|
||||
floors = SGMisc<double>::round(_material->get_building_large_min_floors() + mt_rand(&seed) * (_material->get_building_large_max_floors() - _material->get_building_large_min_floors()));
|
||||
height = floors * (2.8 + mt_rand(&seed));
|
||||
pitch_height = (mt_rand(&seed) < material->get_building_large_pitch()) ? 3.0 : 0.0;
|
||||
pitch_height = (mt_rand(&seed) < _material->get_building_large_pitch()) ? 3.0 : 0.0;
|
||||
|
||||
if (pitch_height == 0.0) {
|
||||
roof_shape = 0;
|
||||
@@ -718,36 +736,35 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
}
|
||||
|
||||
SGBuildingBin::BuildingType SGBuildingBin::getBuildingType(float roll) {
|
||||
if (roll < _material->get_building_small_fraction()) {
|
||||
return SGBuildingBin::SMALL;
|
||||
}
|
||||
|
||||
if (roll < material->get_building_small_fraction()) {
|
||||
return SGBuildingBin::SMALL;
|
||||
}
|
||||
|
||||
if (roll < (material->get_building_small_fraction() + material->get_building_medium_fraction())) {
|
||||
return SGBuildingBin::MEDIUM;
|
||||
}
|
||||
if (roll < (_material->get_building_small_fraction() + _material->get_building_medium_fraction())) {
|
||||
return SGBuildingBin::MEDIUM;
|
||||
}
|
||||
|
||||
return SGBuildingBin::LARGE;
|
||||
}
|
||||
|
||||
float SGBuildingBin::getBuildingMaxRadius(BuildingType type) {
|
||||
if (type == SGBuildingBin::SMALL) return material->get_building_small_max_width();
|
||||
if (type == SGBuildingBin::MEDIUM) return material->get_building_medium_max_width();
|
||||
if (type == SGBuildingBin::LARGE) return material->get_building_large_max_width();
|
||||
return 0;
|
||||
if (type == SGBuildingBin::SMALL) return _material->get_building_small_max_width();
|
||||
if (type == SGBuildingBin::MEDIUM) return _material->get_building_medium_max_width();
|
||||
if (type == SGBuildingBin::LARGE) return _material->get_building_large_max_width();
|
||||
return 0;
|
||||
}
|
||||
|
||||
float SGBuildingBin::getBuildingMaxDepth(BuildingType type) {
|
||||
if (type == SGBuildingBin::SMALL) return material->get_building_small_max_depth();
|
||||
if (type == SGBuildingBin::MEDIUM) return material->get_building_medium_max_depth();
|
||||
if (type == SGBuildingBin::LARGE) return material->get_building_large_max_depth();
|
||||
return 0;
|
||||
if (type == SGBuildingBin::SMALL) return _material->get_building_small_max_depth();
|
||||
if (type == SGBuildingBin::MEDIUM) return _material->get_building_medium_max_depth();
|
||||
if (type == SGBuildingBin::LARGE) return _material->get_building_large_max_depth();
|
||||
return 0;
|
||||
}
|
||||
|
||||
ref_ptr<Group> SGBuildingBin::createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options)
|
||||
{
|
||||
ref_ptr<Effect> effect;
|
||||
EffectMap::iterator iter = buildingEffectMap.find(*texture);
|
||||
auto iter = buildingEffectMap.find(_textureName);
|
||||
|
||||
if ((iter == buildingEffectMap.end())||
|
||||
(!iter->second.lock(effect)))
|
||||
@@ -756,16 +773,14 @@ typedef QuadTreeBuilder<LOD*, SGBuildingBin::BuildingInstance, MakeBuildingLeaf,
|
||||
makeChild(effectProp, "inherits-from")->setStringValue("Effects/building");
|
||||
SGPropertyNode* params = makeChild(effectProp, "parameters");
|
||||
// Main texture - n=0
|
||||
params->getChild("texture", 0, true)->getChild("image", 0, true)
|
||||
->setStringValue(*texture);
|
||||
params->getChild("texture", 0, true)->getChild("image", 0, true)->setStringValue(_textureName);
|
||||
|
||||
// Light map - n=3
|
||||
params->getChild("texture", 3, true)->getChild("image", 0, true)
|
||||
->setStringValue(*lightMap);
|
||||
params->getChild("texture", 3, true)->getChild("image", 0, true)->setStringValue(_lightMapName);
|
||||
|
||||
effect = makeEffect(effectProp, true, options);
|
||||
if (iter == buildingEffectMap.end())
|
||||
buildingEffectMap.insert(EffectMap::value_type(*texture, effect));
|
||||
buildingEffectMap.insert(EffectMap::value_type(_textureName, effect));
|
||||
else
|
||||
iter->second = effect; // update existing, but empty observer
|
||||
}
|
||||
|
||||
@@ -145,20 +145,19 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
const SGSharedPtr<SGMaterial> _material;
|
||||
|
||||
const SGMaterial *material;
|
||||
std::string _materialName;
|
||||
std::string _textureName;
|
||||
std::string _lightMapName;
|
||||
|
||||
std::string* material_name;
|
||||
std::string* texture;
|
||||
std::string* lightMap;
|
||||
|
||||
// Visibility range for buildings
|
||||
float buildingRange;
|
||||
// Visibility range for buildings
|
||||
float buildingRange;
|
||||
|
||||
|
||||
// Information for an instance of a building - position and orientation
|
||||
typedef std::vector<BuildingInstance> BuildingInstanceList;
|
||||
BuildingInstanceList buildingLocations;
|
||||
// Information for an instance of a building - position and orientation
|
||||
typedef std::vector<BuildingInstance> BuildingInstanceList;
|
||||
BuildingInstanceList buildingLocations;
|
||||
|
||||
public:
|
||||
|
||||
@@ -187,7 +186,7 @@ public:
|
||||
|
||||
bool checkMinDist (SGVec3f p, float radius);
|
||||
|
||||
std::string* getMaterialName() { return material_name; }
|
||||
const std::string& getMaterialName() const { return _materialName; }
|
||||
|
||||
BuildingType getBuildingType(float roll);
|
||||
float getBuildingMaxRadius(BuildingType);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <simgear/scene/material/EffectGeode.hxx>
|
||||
#include <simgear/scene/material/mat.hxx>
|
||||
#include <simgear/scene/material/matlib.hxx>
|
||||
#include <simgear/scene/model/BoundingVolumeBuildVisitor.hxx>
|
||||
#include <simgear/scene/util/OsgMath.hxx>
|
||||
#include <simgear/scene/util/VectorArrayAdapter.hxx>
|
||||
#include <simgear/scene/util/SGNodeMasks.hxx>
|
||||
@@ -340,5 +341,10 @@ osg::Node* SGOceanTile(const SGBucket& b, SGMaterialLib *matlib, int latPoints,
|
||||
transform->addChild(geode);
|
||||
transform->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) );
|
||||
|
||||
// Create a BVH at this point. This is normally provided by the file loader, but as we create the
|
||||
// geometry programmatically, no file loader is involved.
|
||||
BoundingVolumeBuildVisitor bvhBuilder(false);
|
||||
transform->accept(bvhBuilder);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
@@ -428,12 +428,12 @@ public:
|
||||
return 0;
|
||||
|
||||
// FIXME: do not include all values here ...
|
||||
osg::Vec3Array* vertices = new osg::Vec3Array;
|
||||
osg::Vec3Array* normals = new osg::Vec3Array;
|
||||
osg::Vec2Array* priTexCoords = new osg::Vec2Array;
|
||||
osg::Vec2Array* secTexCoords = new osg::Vec2Array;
|
||||
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
|
||||
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
|
||||
osg::ref_ptr<osg::Vec2Array> priTexCoords = new osg::Vec2Array;
|
||||
osg::ref_ptr<osg::Vec2Array> secTexCoords = new osg::Vec2Array;
|
||||
|
||||
osg::Vec4Array* colors = new osg::Vec4Array;
|
||||
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
|
||||
colors->push_back(osg::Vec4(1, 1, 1, 1));
|
||||
|
||||
osg::Geometry* geometry = new osg::Geometry;
|
||||
|
||||
@@ -151,14 +151,26 @@ Geometry* makeSharedTreeGeometry(int numQuads)
|
||||
return result;
|
||||
}
|
||||
|
||||
ref_ptr<Geometry> sharedTreeGeometry;
|
||||
static std::mutex static_sharedGeometryMutex;
|
||||
static ref_ptr<Geometry> sharedTreeGeometry;
|
||||
|
||||
void clearSharedTreeGeometry()
|
||||
{
|
||||
std::lock_guard<std::mutex> g(static_sharedGeometryMutex);
|
||||
sharedTreeGeometry = {};
|
||||
}
|
||||
|
||||
Geometry* createTreeGeometry(float width, float height, int varieties)
|
||||
{
|
||||
if (!sharedTreeGeometry)
|
||||
sharedTreeGeometry = makeSharedTreeGeometry(1600);
|
||||
Geometry* quadGeom = simgear::clone(sharedTreeGeometry.get(),
|
||||
CopyOp::SHALLOW_COPY);
|
||||
Geometry* quadGeom = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> g(static_sharedGeometryMutex);
|
||||
if (!sharedTreeGeometry)
|
||||
sharedTreeGeometry = makeSharedTreeGeometry(1600);
|
||||
quadGeom = simgear::clone(sharedTreeGeometry.get(),
|
||||
CopyOp::SHALLOW_COPY);
|
||||
}
|
||||
|
||||
Vec3Array* params = new Vec3Array;
|
||||
params->push_back(Vec3(width, height, (float)varieties));
|
||||
quadGeom->setNormalArray(params);
|
||||
|
||||
@@ -35,11 +35,14 @@ namespace simgear
|
||||
{
|
||||
class TreeBin {
|
||||
public:
|
||||
struct Tree {
|
||||
SGVec3f position;
|
||||
SGVec3f tnormal;
|
||||
Tree(const SGVec3f& p, const SGVec3f& t) : position(p),tnormal(t)
|
||||
{ }
|
||||
~TreeBin() = default;
|
||||
|
||||
struct Tree {
|
||||
SGVec3f position;
|
||||
SGVec3f tnormal;
|
||||
Tree(const SGVec3f& p, const SGVec3f& t) : position(p), tnormal(t)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<Tree> TreeList;
|
||||
@@ -55,19 +58,25 @@ public:
|
||||
{ _trees.push_back(t); }
|
||||
|
||||
void insert(const SGVec3f& p, const SGVec3f& tnorm)
|
||||
{insert(Tree(p,tnorm));}
|
||||
{
|
||||
_trees.emplace_back(p, tnorm);
|
||||
}
|
||||
|
||||
unsigned getNumTrees() const
|
||||
{ return _trees.size(); }
|
||||
|
||||
const Tree& getTree(unsigned i) const
|
||||
{ return _trees[i]; }
|
||||
{
|
||||
assert(i < _trees.size());
|
||||
return _trees.at(i);
|
||||
}
|
||||
|
||||
TreeList _trees;
|
||||
|
||||
~TreeBin() {
|
||||
_trees.clear();
|
||||
}
|
||||
};
|
||||
|
||||
void clearSharedTreeGeometry();
|
||||
|
||||
typedef std::list<TreeBin*> SGTreeBinList;
|
||||
|
||||
osg::Group* createForest(SGTreeBinList& forestList, const osg::Matrix& transform,
|
||||
|
||||
@@ -280,9 +280,18 @@ SGLightFactory::getLights(const SGDirectionalLightBin& lights)
|
||||
//stateSet->setRenderBinDetails(POINT_LIGHTS_BIN, "DepthSortedBin");
|
||||
//stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
|
||||
|
||||
|
||||
static SGSceneFeatures* sceneFeatures = SGSceneFeatures::instance();
|
||||
bool useTriangles = sceneFeatures->getEnableTriangleDirectionalLights();
|
||||
|
||||
osg::DrawArrays* drawArrays;
|
||||
drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
|
||||
0, vertices->size());
|
||||
if (useTriangles)
|
||||
drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES,
|
||||
0, vertices->size());
|
||||
else
|
||||
drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS,
|
||||
0, vertices->size());
|
||||
|
||||
geometry->addPrimitiveSet(drawArrays);
|
||||
return geometry;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user