├── .clang-format ├── .editorconfig ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Demo-App.sln ├── Demo-App ├── CMakeLists.txt ├── Demo-App.vcxproj ├── Demo-App.vcxproj.filters ├── Demo-App.xml ├── c_resource.hpp ├── caboodle-posix.cpp ├── caboodle-program-arguments.cpp ├── caboodle-windows.cpp ├── caboodle.ixx ├── client.ixx ├── events.ixx ├── executor.ixx ├── generator.hpp ├── generator.ixx ├── gui.cpp ├── gui.ixx ├── main.cpp ├── net.cpp ├── net.ixx ├── server.ixx ├── video.ixx ├── videodecoder.cpp ├── videodecoder.ixx └── videoframe.ixx ├── LICENSE ├── README.md └── Slides ├── contemporary-c++-in-action-meeting++2022.pdf ├── contemporary-c-in-action-adc2024.pdf ├── contemporary-c-in-action-cppcon2022.pdf ├── so-you-want-to-use-c++-modules-cross-platform-ndc-techtown2023.pdf ├── so-you-want-to-use-c-modules-cross-platform-meeting-cpp-2023.pdf └── so-you-want-to-use-modules-cross-plattform-cpponsea2023.pdf /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | Standard: Latest 4 | BasedOnStyle: LLVM 5 | ColumnLimit: 90 6 | IndentWidth: 4 7 | TabWidth: 4 8 | UseTab: ForIndentation 9 | AccessModifierOffset: -4 10 | AlignConsecutiveAssignments: 11 | Enabled: true 12 | AlignCompound: true 13 | PadOperators: false 14 | AlignConsecutiveBitFields: 15 | Enabled: true 16 | AllowShortBlocksOnASingleLine: Empty 17 | AllowShortCaseLabelsOnASingleLine: true 18 | AllowShortFunctionsOnASingleLine: Inline 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLambdasOnASingleLine: Empty 21 | AlwaysBreakTemplateDeclarations: Yes 22 | BreakConstructorInitializers: BeforeComma 23 | BreakBeforeBraces: Custom 24 | BraceWrapping: 25 | SplitEmptyFunction: false 26 | SplitEmptyRecord: false 27 | SplitEmptyNamespace: false 28 | ConstructorInitializerIndentWidth: 0 29 | Cpp11BracedListStyle: false 30 | CompactNamespaces: true 31 | IndentCaseLabels: true 32 | IndentRequiresClause: true 33 | IndentPPDirectives: AfterHash 34 | KeepEmptyLinesAtTheStartOfBlocks: false 35 | PointerAlignment: Middle 36 | SortIncludes: true 37 | IncludeBlocks: Preserve 38 | IncludeCategories: 39 | # 'stdafx.h' must come first if present 40 | - Regex: 'stdafx.h' 41 | Priority: -1 42 | # Qt Headers in <> without extension. 43 | - Regex: '<(Q|Qt)[A-Z][A-Za-z0-9]+>' 44 | Priority: 4 45 | # Headers in <> without extension. 46 | - Regex: '<([A-Za-z0-9\Q/-_\E])+>' 47 | Priority: 6 48 | # Headers in <> from specific external libraries. 49 | - Regex: '<(boost)\/.+>' 50 | Priority: 5 51 | # Headers in <> with extension. 52 | - Regex: '<([A-Za-z0-9.\Q/-_\E])+>' 53 | Priority: 3 54 | # Qt ui_ Headers. 55 | - Regex: '"ui_[A-Za-z0-9]+\.hpp"' 56 | Priority: 1 57 | # Headers in "" with extension. 58 | - Regex: '"([A-Za-z0-9.\Q/-_\E])+"' 59 | Priority: 2 60 | ... 61 | 62 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # all files 4 | [*] 5 | end_of_line = crlf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | # C/C++ 10 | [*.{c,cpp,cxx,h,hpp,hxx,ipp}] 11 | indent_style = tab 12 | indent_size = 4 13 | charset = utf-8-bom 14 | 15 | # Translation Portable Object 16 | [*.po] 17 | end_of_line = lf 18 | charset = utf-8 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Win32 2 | x64 3 | *.user 4 | .vs 5 | /asio 6 | /boost* 7 | /include 8 | /libavcodec 9 | /libavformat 10 | /libavfilter 11 | /libavutil 12 | /libav 13 | /msvc-stl 14 | /SDL* 15 | /lib* 16 | /*test 17 | /bin* 18 | /bmi* 19 | /*.props 20 | /*.c* 21 | /*.gif 22 | /Demo-App/P*.hpp 23 | /Demo-App/*.zip 24 | /*.sh 25 | media 26 | bld/ 27 | build*/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "asio"] 2 | path = asio 3 | url = https://github.com/DanielaE/asio.git 4 | [submodule "argparse"] 5 | path = argparse 6 | url = https://github.com/DanielaE/argparse.git 7 | [submodule "libav"] 8 | path = libav 9 | url = https://github.com/DanielaE/libav.module.git 10 | [submodule "stl"] 11 | path = stl 12 | url = https://github.com/DanielaE/std.module.git 13 | [submodule "SDL"] 14 | path = SDL 15 | url = https://github.com/DanielaE/SDL.git 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.26) 2 | 3 | if (CMAKE_CXX_STANDARD LESS 20) 4 | message(FATAL_ERROR "At least C++20 required but have ${CMAKE_CXX_STANDARD}") 5 | endif() 6 | 7 | project(CppInAction 8 | DESCRIPTION "Contemporary C++ in Action demo project" 9 | HOMEPAGE_URL "https://github.com/DanielaE/CppInAction" 10 | LANGUAGES CXX 11 | ) 12 | 13 | if (CMAKE_VERSION VERSION_LESS 3.28) 14 | if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.27) 15 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7") 16 | else () 17 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a") 18 | endif() 19 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1) 20 | else() 21 | cmake_policy(VERSION 3.28) 22 | endif() 23 | set(CMAKE_CXX_EXTENSIONS OFF) 24 | set(CMAKE_CXX_STANDARD 23) 25 | 26 | add_subdirectory(argparse) 27 | add_subdirectory(asio) 28 | add_subdirectory(libav) 29 | add_subdirectory(SDL) 30 | add_subdirectory(stl) 31 | add_subdirectory(Demo-App) 32 | -------------------------------------------------------------------------------- /Demo-App.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32519.111 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Demo-App", "Demo-App\Demo-App.vcxproj", "{DF4B393A-367F-4F69-8DF8-94775EF8FB36}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rd-party-modules", "3rd-party-modules", "{074EACD6-9A2F-4FEF-B193-9998B0136AB5}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "argparse", "..\argparse\module\argparse.vcxproj", "{066847E0-D195-4FDE-A183-2484514AB543}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "asio", "..\asio\asio\module\asio.vcxproj", "{1569001E-8080-4A44-93FF-CA44C1554BC6}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libav", "..\libav.module\module\libav.vcxproj", "{1569001E-8080-4A44-93FF-C544C13D45F6}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdl", "..\SDL\module\sdl2.vcxproj", "{1569001E-8080-4A44-93FF-CA44C15545C6}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "std", "stl\std.vcxproj", "{B02D2540-5B5F-4348-BE2A-D28099AECD09}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|x64 = Debug|x64 23 | Release|x64 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {DF4B393A-367F-4F69-8DF8-94775EF8FB36}.Debug|x64.ActiveCfg = Debug|x64 27 | {DF4B393A-367F-4F69-8DF8-94775EF8FB36}.Debug|x64.Build.0 = Debug|x64 28 | {DF4B393A-367F-4F69-8DF8-94775EF8FB36}.Release|x64.ActiveCfg = Release|x64 29 | {DF4B393A-367F-4F69-8DF8-94775EF8FB36}.Release|x64.Build.0 = Release|x64 30 | {066847E0-D195-4FDE-A183-2484514AB543}.Debug|x64.ActiveCfg = Debug|x64 31 | {066847E0-D195-4FDE-A183-2484514AB543}.Debug|x64.Build.0 = Debug|x64 32 | {066847E0-D195-4FDE-A183-2484514AB543}.Release|x64.ActiveCfg = Release|x64 33 | {066847E0-D195-4FDE-A183-2484514AB543}.Release|x64.Build.0 = Release|x64 34 | {1569001E-8080-4A44-93FF-CA44C1554BC6}.Debug|x64.ActiveCfg = Debug|x64 35 | {1569001E-8080-4A44-93FF-CA44C1554BC6}.Debug|x64.Build.0 = Debug|x64 36 | {1569001E-8080-4A44-93FF-CA44C1554BC6}.Release|x64.ActiveCfg = Release|x64 37 | {1569001E-8080-4A44-93FF-CA44C1554BC6}.Release|x64.Build.0 = Release|x64 38 | {1569001E-8080-4A44-93FF-C544C13D45F6}.Debug|x64.ActiveCfg = Debug|x64 39 | {1569001E-8080-4A44-93FF-C544C13D45F6}.Debug|x64.Build.0 = Debug|x64 40 | {1569001E-8080-4A44-93FF-C544C13D45F6}.Release|x64.ActiveCfg = Release|x64 41 | {1569001E-8080-4A44-93FF-C544C13D45F6}.Release|x64.Build.0 = Release|x64 42 | {1569001E-8080-4A44-93FF-CA44C15545C6}.Debug|x64.ActiveCfg = Debug|x64 43 | {1569001E-8080-4A44-93FF-CA44C15545C6}.Debug|x64.Build.0 = Debug|x64 44 | {1569001E-8080-4A44-93FF-CA44C15545C6}.Release|x64.ActiveCfg = Release|x64 45 | {1569001E-8080-4A44-93FF-CA44C15545C6}.Release|x64.Build.0 = Release|x64 46 | {B02D2540-5B5F-4348-BE2A-D28099AECD09}.Debug|x64.ActiveCfg = Debug|x64 47 | {B02D2540-5B5F-4348-BE2A-D28099AECD09}.Debug|x64.Build.0 = Debug|x64 48 | {B02D2540-5B5F-4348-BE2A-D28099AECD09}.Release|x64.ActiveCfg = Release|x64 49 | {B02D2540-5B5F-4348-BE2A-D28099AECD09}.Release|x64.Build.0 = Release|x64 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(NestedProjects) = preSolution 55 | {066847E0-D195-4FDE-A183-2484514AB543} = {074EACD6-9A2F-4FEF-B193-9998B0136AB5} 56 | {1569001E-8080-4A44-93FF-CA44C1554BC6} = {074EACD6-9A2F-4FEF-B193-9998B0136AB5} 57 | {1569001E-8080-4A44-93FF-C544C13D45F6} = {074EACD6-9A2F-4FEF-B193-9998B0136AB5} 58 | {1569001E-8080-4A44-93FF-CA44C15545C6} = {074EACD6-9A2F-4FEF-B193-9998B0136AB5} 59 | {B02D2540-5B5F-4348-BE2A-D28099AECD09} = {074EACD6-9A2F-4FEF-B193-9998B0136AB5} 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {69EB57EB-41BD-45B1-97B2-EF2661846A8B} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /Demo-App/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.26) 2 | 3 | if (CMAKE_CXX_STANDARD LESS 20) 4 | message(FATAL_ERROR "At least C++20 required but have ${CMAKE_CXX_STANDARD}") 5 | endif() 6 | 7 | project(std 8 | DESCRIPTION "Contemporary C++ in Action demo app" 9 | LANGUAGES CXX 10 | ) 11 | 12 | if (CMAKE_VERSION VERSION_LESS 3.28) 13 | if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.27) 14 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7") 15 | else () 16 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a") 17 | endif() 18 | set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1) 19 | else() 20 | cmake_policy(VERSION 3.28) 21 | endif() 22 | set(CMAKE_CXX_EXTENSIONS OFF) 23 | set(CXX_STANDARD_REQUIRED ON) 24 | 25 | add_executable(demo ) 26 | 27 | if (MSVC) 28 | # I do mean C++23 29 | target_compile_options(demo PUBLIC /utf-8 /Zc:__cplusplus /Zc:throwingNew /Zc:inline /Zc:externConstexpr /Zc:templateScope /Zc:checkGwOdr /Zc:enumTypes) 30 | target_compile_features(demo PUBLIC cxx_std_23) 31 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 32 | set(CMAKE_CXX_STANDARD 20) 33 | target_compile_features(demo PUBLIC cxx_std_23) 34 | # I do mean C++20 35 | target_compile_options(demo PUBLIC -fsized-deallocation -faligned-allocation) 36 | else() 37 | # these are the desired defaults 38 | target_compile_features(demo PUBLIC cxx_std_23) 39 | endif() 40 | 41 | set(module-if 42 | caboodle.ixx client.ixx events.ixx executor.ixx gui.ixx net.ixx 43 | server.ixx video.ixx videodecoder.ixx videoframe.ixx) 44 | set(module-internal-partitions videodecoder.cpp) 45 | set(agnostic-module-impl 46 | caboodle-program-arguments.cpp gui.cpp net.cpp) 47 | set(Posix-module-impl caboodle-posix.cpp) 48 | set(Windows-module-impl caboodle-windows.cpp) 49 | set(header-units c_resource.hpp) 50 | 51 | target_sources(demo 52 | PRIVATE main.cpp ${agnostic-module-impl} 53 | PRIVATE 54 | FILE_SET modules TYPE CXX_MODULES 55 | FILES ${module-if} ${module-internal-partitions} 56 | ) 57 | if (MSVC) 58 | target_sources(demo PRIVATE ${Windows-module-impl}) 59 | target_sources(demo PRIVATE Demo-App.xml) 60 | else() 61 | target_sources(demo PRIVATE ${Posix-module-impl}) 62 | endif() 63 | 64 | target_link_libraries(demo PRIVATE argparse asio libav sdl std) 65 | -------------------------------------------------------------------------------- /Demo-App/Demo-App.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | {DF4B393A-367F-4F69-8DF8-94775EF8FB36} 15 | Win32Proj 16 | $(DefaultWindowsSDKVersion) 17 | 18 | 19 | 20 | Application 21 | Unicode 22 | true 23 | true 24 | $(DefaultPlatformToolset) 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | true 34 | 35 | 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | CompileAsCppModuleInternalPartition 53 | false 54 | 55 | 56 | CompileAsHeaderUnit 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {066847e0-d195-4fde-a183-2484514ab543} 65 | 66 | 67 | {1569001e-8080-4a44-93ff-ca44c1554bc6} 68 | 69 | 70 | {1569001e-8080-4a44-93ff-c544c13d45f6} 71 | 72 | 73 | {1569001e-8080-4a44-93ff-ca44c15545c6} 74 | 75 | 76 | {b02d2540-5b5f-4348-be2a-d28099aecd09} 77 | 78 | 79 | 80 | 81 | 4127;4702;5050 82 | /headerUnit $(SolutionDir)\msvc-stl\allstd.hpp=allstd.hpp.ifc %(AdditionalOptions) 83 | 84 | 85 | Console 86 | /Ignore:4199 %(AdditionalOptions) 87 | 88 | 89 | Demo-App.xml %(AdditionalManifestFiles) 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /Demo-App/Demo-App.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {14e9f923-ce3f-41f2-bd8b-1901ef5d779a} 18 | ixx 19 | 20 | 21 | 22 | 23 | Source Files 24 | 25 | 26 | Modules 27 | 28 | 29 | Modules 30 | 31 | 32 | Modules 33 | 34 | 35 | Modules 36 | 37 | 38 | Modules 39 | 40 | 41 | Modules 42 | 43 | 44 | Modules 45 | 46 | 47 | Modules 48 | 49 | 50 | Modules 51 | 52 | 53 | Modules 54 | 55 | 56 | Modules 57 | 58 | 59 | Modules 60 | 61 | 62 | Modules 63 | 64 | 65 | Modules 66 | 67 | 68 | Modules 69 | 70 | 71 | Modules 72 | 73 | 74 | Modules 75 | 76 | 77 | 78 | 79 | Resource Files 80 | 81 | 82 | -------------------------------------------------------------------------------- /Demo-App/Demo-App.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UTF-8 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Demo-App/c_resource.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #if __cpp_lib_modules >= 202207L 4 | import std; 5 | #else 6 | # include 7 | # include 8 | # include 9 | #endif 10 | 11 | // Wrap C-style 'things' that are allocated in dynamic memory and their related APIs 12 | // 13 | // Instead of passing around (const) 'thing' pointers, and manually constructing or 14 | // destructing them, provide proper C++ 'thing' object types with move-only value 15 | // semantics that bundles the respective managing functions from the C-style API. These 16 | // wrappers act as drop-in replacements with correct constness and uniqueness wherever 17 | // formerly the bare 'thing' pointers were used. The API sembles for the most part a 18 | // std::unique_ptr but goes beyond that in terms of semantic correctness and 19 | // convenience. 20 | 21 | namespace stdex { 22 | 23 | // custumization point to support different 'null' values 24 | template 25 | constexpr inline T * c_resource_null_value = nullptr; 26 | 27 | // constructors and destructors support two API schemata of the underlying managing 28 | // functions: 29 | // 30 | // schema 1: 31 | // constructor functions return the constructed entity as an output value, 32 | // e.g. thing * construct(); 33 | // destructor functions take the disposable entity by an input value, 34 | // e.g. void destruct(thing *); 35 | // 36 | // schema 2: 37 | // both constructor and destructor functions take an input-output reference to the thing 38 | // pointer, 39 | // e.g. void construct(thing **); 40 | // void destruct(thing **); 41 | // and modify the referenced thing pointer accordingly. If the constructor functions for 42 | // some call signatures are non-void, no constructors exist for these signatures. 43 | // 44 | // modifiers, 45 | // e.g. replace(...); 46 | // exist only for schema 2 and act like constructors but return the return value of the 47 | // underlying construct function, 48 | // e.g. auto construct(thing **); 49 | 50 | template 51 | struct c_resource { 52 | using pointer = T *; 53 | using const_pointer = std::add_const_t *; 54 | using element_type = T; 55 | 56 | private: 57 | using Constructor = decltype(ConstructFunction); 58 | using Destructor = decltype(DestructFunction); 59 | 60 | static_assert(std::is_function_v>, 61 | "I need a C function"); 62 | static_assert(std::is_function_v>, 63 | "I need a C function"); 64 | 65 | static constexpr Constructor construct = ConstructFunction; 66 | static constexpr Destructor destruct = DestructFunction; 67 | static constexpr T * null = c_resource_null_value; 68 | 69 | struct construct_t {}; 70 | 71 | public: 72 | static constexpr construct_t constructed = {}; 73 | 74 | [[nodiscard]] constexpr c_resource() noexcept = default; 75 | [[nodiscard]] constexpr explicit c_resource(construct_t) noexcept 76 | requires std::is_invocable_r_v 77 | : ptr_{ construct() } {} 78 | 79 | template 80 | requires(sizeof...(Ts) > 0 && std::is_invocable_r_v) 81 | [[nodiscard]] constexpr explicit(sizeof...(Ts) == 1) 82 | c_resource(Ts &&... Args) noexcept 83 | : ptr_{ construct(static_cast(Args)...) } {} 84 | 85 | template 86 | requires(sizeof...(Ts) > 0 && 87 | requires(T * p, Ts... Args) { 88 | { construct(&p, Args...) } -> std::same_as; 89 | }) 90 | [[nodiscard]] constexpr explicit(sizeof...(Ts) == 1) 91 | c_resource(Ts &&... Args) noexcept 92 | : ptr_{ null } { 93 | construct(&ptr_, static_cast(Args)...); 94 | } 95 | 96 | template 97 | requires(std::is_invocable_v) 98 | [[nodiscard]] constexpr auto emplace(Ts &&... Args) noexcept { 99 | _destruct(ptr_); 100 | ptr_ = null; 101 | return construct(&ptr_, static_cast(Args)...); 102 | } 103 | 104 | [[nodiscard]] constexpr c_resource(c_resource && other) noexcept { 105 | ptr_ = other.ptr_; 106 | other.ptr_ = null; 107 | }; 108 | constexpr c_resource & operator=(c_resource && rhs) noexcept { 109 | if (this != &rhs) { 110 | _destruct(ptr_); 111 | ptr_ = rhs.ptr_; 112 | rhs.ptr_ = null; 113 | } 114 | return *this; 115 | }; 116 | constexpr void swap(c_resource & other) noexcept { 117 | auto ptr = ptr_; 118 | ptr_ = other.ptr_; 119 | other.ptr_ = ptr; 120 | } 121 | 122 | static constexpr bool destructible = 123 | std::is_invocable_v || std::is_invocable_v; 124 | 125 | constexpr ~c_resource() noexcept = delete; 126 | constexpr ~c_resource() noexcept 127 | requires destructible 128 | { 129 | _destruct(ptr_); 130 | } 131 | constexpr void clear() noexcept 132 | requires destructible 133 | { 134 | _destruct(ptr_); 135 | ptr_ = null; 136 | } 137 | constexpr c_resource & operator=(std::nullptr_t) noexcept { 138 | clear(); 139 | return *this; 140 | } 141 | 142 | [[nodiscard]] constexpr explicit operator bool() const noexcept { 143 | return ptr_ != null; 144 | } 145 | [[nodiscard]] constexpr bool empty() const noexcept { return ptr_ == null; } 146 | [[nodiscard]] constexpr friend bool have(const c_resource & r) noexcept { 147 | return r.ptr_ != null; 148 | } 149 | 150 | auto operator<=>(const c_resource &) = delete; 151 | [[nodiscard]] bool operator==(const c_resource & rhs) const noexcept { 152 | return 0 == std::memcmp(ptr_, rhs.ptr_, sizeof(T)); 153 | } 154 | 155 | #if defined(__cpp_explicit_this_parameter) 156 | template 157 | static constexpr bool less_const = std::is_const_v < std::is_const_v; 158 | template 159 | static constexpr bool similar = std::is_same_v, T>; 160 | 161 | template 162 | requires(similar && !less_const) 163 | [[nodiscard]] constexpr operator U *(this Self && self) noexcept { 164 | return std::forward_like(self.ptr_); 165 | } 166 | [[nodiscard]] constexpr auto operator->(this auto && self) noexcept { 167 | return std::forward_like(self.ptr_); 168 | } 169 | [[nodiscard]] constexpr auto get(this auto && self) noexcept { 170 | return std::forward_like(self.ptr_); 171 | } 172 | #else 173 | [[nodiscard]] constexpr operator pointer() noexcept { return like(*this); } 174 | [[nodiscard]] constexpr operator const_pointer() const noexcept { 175 | return like(*this); 176 | } 177 | [[nodiscard]] constexpr pointer operator->() noexcept { return like(*this); } 178 | [[nodiscard]] constexpr const_pointer operator->() const noexcept { 179 | return like(*this); 180 | } 181 | [[nodiscard]] constexpr pointer get() noexcept { return like(*this); } 182 | [[nodiscard]] constexpr const_pointer get() const noexcept { return like(*this); } 183 | 184 | private: 185 | static constexpr auto like(c_resource & self) noexcept { return self.ptr_; } 186 | static constexpr auto like(const c_resource & self) noexcept { 187 | return static_cast(self.ptr_); 188 | } 189 | 190 | public: 191 | #endif 192 | 193 | constexpr void reset(pointer ptr = null) noexcept { 194 | _destruct(ptr_); 195 | ptr_ = ptr; 196 | } 197 | 198 | constexpr pointer release() noexcept { 199 | auto ptr = ptr_; 200 | ptr_ = null; 201 | return ptr; 202 | } 203 | 204 | template 205 | struct guard { 206 | using cleaner = decltype(CleanupFunction); 207 | 208 | static_assert(std::is_function_v>, 209 | "I need a C function"); 210 | static_assert(std::is_invocable_v, "Please check the function"); 211 | 212 | constexpr guard(c_resource & Obj) noexcept 213 | : ptr_{ Obj.ptr_ } {} 214 | constexpr ~guard() noexcept { 215 | if (ptr_ != null) 216 | CleanupFunction(ptr_); 217 | } 218 | 219 | private: 220 | pointer ptr_; 221 | }; 222 | 223 | private: 224 | constexpr static void _destruct(pointer & p) noexcept 225 | requires std::is_invocable_v 226 | { 227 | if (p != null) 228 | destruct(p); 229 | } 230 | constexpr static void _destruct(pointer & p) noexcept 231 | requires std::is_invocable_v 232 | { 233 | if (p != null) 234 | destruct(&p); 235 | } 236 | 237 | pointer ptr_ = null; 238 | }; 239 | 240 | } // namespace stdex 241 | 242 | #ifdef UNITTEST 243 | 244 | namespace unit_test { 245 | struct S {}; 246 | extern "C" { 247 | S * conS1(); // API schema 1: return value 248 | void desS1(S *); // API schema 1: value as input parameter 249 | void conS2(S **); // API schema 2: reference as input-output parameter 250 | void desS2(S **); // API schema 2: reference as input-output parameter 251 | } 252 | using w1 = stdex::c_resource; 253 | using w2 = stdex::c_resource; 254 | 255 | static_assert(std::is_nothrow_default_constructible_v); 256 | static_assert(!std::is_copy_constructible_v); 257 | static_assert(std::is_nothrow_move_constructible_v); 258 | static_assert(!std::is_copy_assignable_v); 259 | static_assert(std::is_nothrow_move_assignable_v); 260 | static_assert(std::is_nothrow_destructible_v); 261 | static_assert(std::is_nothrow_swappable_v); 262 | 263 | static_assert(std::is_nothrow_default_constructible_v); 264 | static_assert(!std::is_copy_constructible_v); 265 | static_assert(std::is_nothrow_move_constructible_v); 266 | static_assert(!std::is_copy_assignable_v); 267 | static_assert(std::is_nothrow_move_assignable_v); 268 | static_assert(std::is_nothrow_destructible_v); 269 | static_assert(std::is_nothrow_swappable_v); 270 | } // namespace unit_test 271 | 272 | #endif 273 | -------------------------------------------------------------------------------- /Demo-App/caboodle-posix.cpp: -------------------------------------------------------------------------------- 1 | module the.whole.caboodle; 2 | import std; 3 | 4 | namespace caboodle { 5 | 6 | // Copyright (c) 2008-2009 Bjoern Hoehrmann 7 | // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 8 | /* 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 10 | software and associated documentation files (the "Software"), to deal in the Software 11 | without restriction, including without limitation the rights to use, copy, modify, merge, 12 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 13 | to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or 16 | substantial portions of the Software.*/ 17 | 18 | enum utf8 : uint8_t { accept, reject }; 19 | 20 | // clang-format off 21 | static constexpr uint8_t utf8d[] = { 22 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 23 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 24 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f 25 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f 26 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f 27 | 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf 28 | 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df 29 | 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef 30 | 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff 31 | 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 32 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 33 | 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 34 | 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 35 | 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 36 | }; 37 | // clang-format on 38 | 39 | constexpr utf8 decode(utf8 state, char codeunit) { 40 | const auto type = utf8d[codeunit & 0xFF]; 41 | return utf8{ utf8d[256u + state * 16u + type] }; 42 | } 43 | 44 | constexpr auto sanitized(std::string_view Input) -> std::string { 45 | std::string Result; 46 | Result.reserve(Input.size()); 47 | 48 | auto State = utf8::accept; 49 | while (!Input.empty()) { 50 | uint8_t Length = 0; 51 | 52 | for (auto Char : Input) { 53 | switch (const auto Next = decode(State, Char)) { 54 | case accept: 55 | Result.append(Input, 0, Length + 1); 56 | State = utf8::accept; 57 | break; 58 | case reject: 59 | Result.append({ '\xEF', '\xBF', '\xBD' }); // '�' 60 | if (State != utf8::accept) 61 | Length = State / 3u; 62 | State = utf8::accept; 63 | break; 64 | default: 65 | ++Length; 66 | State = Next; 67 | break; 68 | } 69 | if (State == utf8::accept) { 70 | Input.remove_prefix(Length + 1); 71 | break; 72 | } 73 | } 74 | } 75 | 76 | return Result; 77 | } 78 | 79 | // filenames have no guaranteed character encoding in POSIX. 80 | // return a sanitized utf-8 encoded string 81 | auto utf8Path(const std::filesystem::path & Path) -> std::string { 82 | return sanitized(Path.generic_string()); 83 | } 84 | 85 | } // namespace caboodle 86 | -------------------------------------------------------------------------------- /Demo-App/caboodle-program-arguments.cpp: -------------------------------------------------------------------------------- 1 | module the.whole.caboodle; 2 | import std; 3 | 4 | import argparse; 5 | 6 | namespace caboodle { 7 | 8 | auto getOptions(int argc, char * argv[]) -> tOptions { 9 | argparse::ArgumentParser Options("Demo application", "", 10 | argparse::default_arguments::help, false); 11 | Options.add_argument("media", "-m", "--media") 12 | .help("media directory") 13 | .default_value("media"); 14 | Options.add_argument("server", "-s", "--server") 15 | .help("server name or ip") 16 | .default_value(""); 17 | 18 | bool needHelp = true; 19 | try { 20 | Options.parse_args(argc, argv); 21 | needHelp = Options.get("--help"); 22 | } catch (...) { /* print help */ 23 | } 24 | if (Options.get("media").contains('?')) 25 | needHelp = true; 26 | 27 | if (needHelp) { 28 | std::println("{}", Options.help().str()); 29 | exit(-1); 30 | } 31 | return { .Media = std::move(Options).get("media"), 32 | .Server = std::move(Options).get("server") }; 33 | } 34 | 35 | } // namespace caboodle 36 | -------------------------------------------------------------------------------- /Demo-App/caboodle-windows.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | 3 | #ifndef _WIN32 4 | # error this is not Windows! 5 | #endif 6 | 7 | module the.whole.caboodle; 8 | import std; 9 | 10 | namespace winapi { 11 | 12 | #define APICALL __declspec(dllimport) __stdcall 13 | 14 | extern "C" { 15 | int APICALL WideCharToMultiByte(unsigned, unsigned long, const wchar_t *, int, char *, 16 | int, const char *, int *); 17 | } 18 | static constexpr auto UTF8 = 65001; 19 | 20 | static inline auto estimateNarrowSize(std::wstring_view U16) noexcept -> std::size_t { 21 | return WideCharToMultiByte(UTF8, 0, U16.data(), static_cast(U16.size()), nullptr, 22 | 0, nullptr, nullptr); 23 | } 24 | static inline auto convertFromWide(std::wstring_view U16) noexcept { 25 | return [&](char * Buffer, std::size_t Size) -> std::size_t { 26 | WideCharToMultiByte(UTF8, 0, U16.data(), static_cast(U16.size()), Buffer, 27 | static_cast(Size), nullptr, nullptr); 28 | return Size; 29 | }; 30 | } 31 | 32 | template 33 | concept canResizeAndOverwrite = 34 | requires(String Str, std::size_t Size, std::size_t (*Callable)(char *, std::size_t)) { 35 | { Str.resize_and_overwrite(Size, Callable) }; 36 | }; 37 | 38 | template 39 | decltype(auto) toUTF8(std::wstring_view Utf16, String && Utf8 = {}) { 40 | Utf8.resize_and_overwrite(estimateNarrowSize(Utf16), convertFromWide(Utf16)); 41 | return static_cast(Utf8); 42 | } 43 | } // namespace winapi 44 | 45 | namespace caboodle { 46 | 47 | // fs::path::string() has unspecified encoding on Windows. 48 | // convert from UTF16 to UTF8 with guaranteed semantics. 49 | auto utf8Path(const std::filesystem::path & Path) -> std::string { 50 | return winapi::toUTF8(Path.wstring()); 51 | } 52 | 53 | } // namespace caboodle 54 | -------------------------------------------------------------------------------- /Demo-App/caboodle.ixx: -------------------------------------------------------------------------------- 1 | export module the.whole.caboodle; 2 | import std; 3 | 4 | namespace caboodle { 5 | 6 | export auto utf8Path(const std::filesystem::path & Path) -> std::string; 7 | 8 | struct tOptions { 9 | std::string Media; 10 | std::string Server; 11 | }; 12 | 13 | export auto getOptions(int argc, char * argv[]) -> tOptions; 14 | 15 | } // namespace caboodle 16 | -------------------------------------------------------------------------------- /Demo-App/client.ixx: -------------------------------------------------------------------------------- 1 | export module client; 2 | import std; 3 | 4 | import asio; 5 | import net; 6 | import gui; 7 | import video; 8 | import executor; 9 | 10 | using namespace std::chrono_literals; 11 | 12 | namespace client { 13 | static constexpr auto ReceiveTimeBudget = 2s; 14 | static constexpr auto ConnectTimeBudget = 2s; 15 | 16 | // a memory resource that owns at least as much memory as it was ever asked to lend out. 17 | 18 | struct AdaptiveMemoryResource { 19 | [[nodiscard]] auto lend(std::size_t Size) -> net::tByteSpan { 20 | if (Size > Capacity_) { 21 | Capacity_ = Size; 22 | Bytes_ = std::make_unique_for_overwrite(Capacity_); 23 | } 24 | return { Bytes_.get(), Size }; 25 | } 26 | 27 | private: 28 | std::unique_ptr Bytes_; 29 | std::size_t Capacity_ = 0; 30 | }; 31 | 32 | // receive a single video frame. 33 | // it returns either 34 | // - a well-formed frame with visible content 35 | // - a well-formed frame without visible content 36 | // - a 'noFrame' placeholder to express disappointment in case of problems 37 | 38 | [[nodiscard]] auto receiveFrame(net::tSocket & Socket, net::tTimer & Timer, 39 | AdaptiveMemoryResource & Memory) 40 | -> asio::awaitable { 41 | alignas(video::FrameHeader) std::byte HeaderBytes[video::FrameHeader::SizeBytes]; 42 | 43 | auto Got = co_await net::receiveFrom(Socket, Timer, HeaderBytes); 44 | if (Got == video::FrameHeader::SizeBytes) { 45 | const auto & Header = *std::start_lifetime_as(HeaderBytes); 46 | 47 | auto Pixels = Memory.lend(Header.SizePixels()); 48 | if (not Pixels.empty()) { 49 | Got = co_await net::receiveFrom(Socket, Timer, Pixels); 50 | Pixels = Pixels.first(Got.value_or(0)); 51 | } 52 | if (Pixels.size() == Header.SizePixels()) 53 | co_return video::Frame{ Header, Pixels }; 54 | } 55 | co_return video::noFrame; 56 | } 57 | 58 | // present a possibly infinite sequence of video frames until the spectator 59 | // gets bored or problems arise. 60 | 61 | [[nodiscard]] auto rollVideos(net::tSocket Socket, net::tTimer Timer, 62 | gui::FancyWindow Window) -> asio::awaitable { 63 | const auto WatchDog = executor::abort(Socket, Timer); 64 | AdaptiveMemoryResource PixelMemory; 65 | 66 | while (Socket.is_open()) { 67 | Timer.expires_after(ReceiveTimeBudget); 68 | const auto Frame = co_await receiveFrame(Socket, Timer, PixelMemory); 69 | const auto & Header = Frame.Header_; 70 | if (Header.isNoFrame()) 71 | break; 72 | 73 | Window.updateFrom(Header); 74 | Window.present(Frame.Pixels_); 75 | 76 | using namespace std::chrono; 77 | 78 | if (Header.isFiller()) 79 | std::println("filler frame"); 80 | else 81 | std::println("frame {:3} {}x{} @ {:>6%Q%q}", Header.Sequence_, Header.Width_, 82 | Header.Height_, round(Header.Timestamp_)); 83 | } 84 | } 85 | 86 | // connects to the server and starts the top-level video receive-render-present loop. 87 | // initiates an application stop in case of communication problems. 88 | 89 | export [[nodiscard]] auto showVideos(asio::io_context & Context, gui::FancyWindow Window, 90 | net::tEndpoints Endpoints) -> asio::awaitable { 91 | net::tTimer Timer(Context); 92 | Timer.expires_after(ConnectTimeBudget); 93 | if (net::tExpectSocket Socket = co_await net::connectTo(Endpoints, Timer)) { 94 | co_await rollVideos(std::move(Socket).value(), std::move(Timer), 95 | std::move(Window)); 96 | } 97 | executor::StopAssetOf(Context).request_stop(); 98 | } 99 | } // namespace client 100 | -------------------------------------------------------------------------------- /Demo-App/events.ixx: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | export module events; 5 | import std; 6 | 7 | import asio; 8 | import executor; 9 | import gui; 10 | import net; 11 | 12 | using namespace std::chrono_literals; 13 | 14 | static constexpr auto EventPollInterval = 50ms; 15 | 16 | // user interaction 17 | export namespace handleEvents { 18 | 19 | // watch out for an interrupt signal (e.g. from the command line). 20 | // initiate an application stop in that case. 21 | 22 | [[nodiscard]] auto fromTerminal(asio::io_context & Context) -> asio::awaitable { 23 | asio::signal_set Signals(Context, SIGINT, SIGTERM); 24 | const auto WatchDog = executor::abort(Signals); 25 | 26 | co_await Signals.async_wait(asio::use_awaitable); 27 | executor::StopAssetOf(Context).request_stop(); 28 | } 29 | 30 | // the GUI interaction is a separate coroutine. 31 | // initiate an application stop if the spectator closes the window. 32 | 33 | [[nodiscard]] auto fromGUI(asio::io_context & Context) -> asio::awaitable { 34 | net::tTimer Timer(Context); 35 | const auto WatchDog = executor::abort(Timer); 36 | 37 | do { 38 | Timer.expires_after(EventPollInterval); 39 | } while (co_await net::expired(Timer) and gui::isAlive()); 40 | executor::StopAssetOf(Context).request_stop(); 41 | } 42 | 43 | } // namespace handleEvents 44 | -------------------------------------------------------------------------------- /Demo-App/executor.ixx: -------------------------------------------------------------------------------- 1 | export module executor; 2 | import std; 3 | 4 | import asio; 5 | 6 | // Convenience types and functions to deal with the executor part of the 7 | // Asio library https://think-async.com/Asio/ 8 | 9 | // The Asio library is also the reference implementation of the 10 | // Networking TS (ISO/IEC TS 19216:2018, C++ Extensions for Library Fundamentals) 11 | // Draft https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf 12 | // plus P0958, P1322, P1943, P2444 13 | // plus executors as decribed in P0443, P1348, and P1393 14 | 15 | namespace executor { 16 | template 17 | constexpr inline bool Unfortunate = false; 18 | template 19 | constexpr inline bool isExecutionContext = std::is_base_of_v; 20 | template 21 | constexpr inline bool isExecutor = 22 | asio::is_executor::value or asio::execution::is_executor::value; 23 | template 24 | constexpr inline bool hasExecutor = requires(T t) { 25 | { t.get_executor() }; 26 | }; 27 | 28 | auto onException(std::stop_source Stop) { 29 | return [Stop_ = std::move(Stop)](std::exception_ptr pEx) mutable { 30 | if (pEx) 31 | Stop_.request_stop(); 32 | }; 33 | } 34 | 35 | using ServiceBase = asio::execution_context::service; 36 | struct StopService : ServiceBase { 37 | using key_type = StopService; 38 | 39 | static asio::io_context::id id; 40 | 41 | using ServiceBase::ServiceBase; 42 | StopService(asio::execution_context & Ctx, std::stop_source Stop) 43 | : ServiceBase(Ctx) 44 | , Stop_(std::move(Stop)) {} 45 | 46 | std::stop_source get() const { return Stop_; } 47 | bool isStopped() const { return Stop_.stop_requested(); } 48 | void requestStop() { Stop_.request_stop(); } 49 | 50 | private: 51 | void shutdown() noexcept override {} 52 | std::stop_source Stop_; 53 | }; 54 | 55 | void addStopService(asio::execution_context & Executor, std::stop_source & Stop) { 56 | asio::make_service(Executor, Stop); 57 | } 58 | 59 | // precondition: 'Context' provides a 'StopService' 60 | 61 | std::stop_source getStop(asio::execution_context & Context) { 62 | return asio::use_service(Context).get(); 63 | } 64 | 65 | template 66 | asio::execution_context & getContext(T & Object) noexcept { 67 | if constexpr (isExecutionContext) 68 | return Object; 69 | else if constexpr (isExecutor) 70 | return Object.context(); 71 | else if constexpr (hasExecutor) 72 | return Object.get_executor().context(); 73 | else 74 | static_assert(Unfortunate, "Please give me an execution context"); 75 | } 76 | 77 | // return the stop_source that is 'wired' to the given 'Object' 78 | 79 | export [[nodiscard]] auto StopAssetOf(auto & Object) { 80 | return executor::getStop(executor::getContext(Object)); 81 | } 82 | 83 | template 84 | static constexpr bool isAwaitable = false; 85 | template 86 | static constexpr bool isAwaitable> = true; 87 | 88 | // A compile-time function that answers the questions if a given callable 'Func' 89 | // - can be called with a set of argument types 'Ts' 90 | // - returns an awaitable 91 | // - must be called synchronously or asynchronously 92 | 93 | template 94 | struct isCallable { 95 | using ReturnType = std::invoke_result_t; 96 | static constexpr bool invocable = std::is_invocable_v; 97 | static constexpr bool returnsAwaitable = isAwaitable; 98 | static constexpr bool synchronously = invocable and not returnsAwaitable; 99 | static constexpr bool asynchronously = invocable and returnsAwaitable; 100 | }; 101 | 102 | // initiate independent asynchronous execution of a piece of work on a given executor. 103 | // signal stop if an exception escapes that piece of work. 104 | 105 | #define WORKITEM std::invoke(std::forward(Work), std::forward(Args)...) 106 | 107 | export template 108 | requires(isCallable::asynchronously) 109 | void commission(auto && Executor, Func && Work, Ts &&... Args) { 110 | auto Stop = executor::StopAssetOf(Executor); 111 | asio::co_spawn(Executor, WORKITEM, executor::onException(Stop)); 112 | } 113 | 114 | // create a scheduler that can issue pieces of work onto the given execution context. 115 | // depending on the kind of work, it is scheduled for synchronous or asynchronous 116 | // execution. 117 | // the execution context is augmented by a stop service related to the given 118 | // stop_source. 119 | 120 | #define WORK std::forward(Work), Context, std::forward(Args)... 121 | 122 | export [[nodiscard]] auto makeScheduler(asio::io_context & Context, 123 | std::stop_source & Stop) { 124 | executor::addStopService(Context, Stop); 125 | 126 | return [&](Func && Work, Ts &&... Args) { 127 | using mustBeCalled = isCallable; 128 | if constexpr (mustBeCalled::asynchronously) 129 | executor::commission(Context, WORK); 130 | else if constexpr (mustBeCalled::synchronously) 131 | return std::invoke(WORK); 132 | else 133 | static_assert(Unfortunate, 134 | "Please help, I don't know how to execute this 'Work'"); 135 | }; 136 | } 137 | 138 | // abort operation of a given object depending on its capabilities. 139 | // the close() operation is customizable to cater for more involved closing requirements. 140 | 141 | #define THIS_WORKS(x) \ 142 | (requires(T Object) { \ 143 | { x }; \ 144 | }) x; 145 | 146 | // clang-format off 147 | template 148 | void _abort(T & Object) { 149 | if constexpr THIS_WORKS( close(Object) ) 150 | else if constexpr THIS_WORKS( Object.close() ) 151 | else if constexpr THIS_WORKS( Object.cancel() ) 152 | else 153 | static_assert(Unfortunate, "Please tell me how to abort on this 'Object'"); 154 | } 155 | // clang-format on 156 | 157 | // create an object that is wired up to abort the operation of all given objects 158 | // whenever a stop is indicated. 159 | 160 | export [[nodiscard]] auto abort(auto & Object, auto &... moreObjects) { 161 | return std::stop_callback{ StopAssetOf(Object).get_token(), [&] { 162 | (_abort(Object), ..., _abort(moreObjects)); 163 | } }; 164 | } 165 | 166 | } // namespace executor 167 | -------------------------------------------------------------------------------- /Demo-App/generator.hpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////// 2 | // Reference implementation of std::generator proposal P2502R2 3 | // https://godbolt.org/z/5hcaPcfvP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef _MSC_VER 18 | #define EMPTY_BASES __declspec(empty_bases) 19 | #ifdef __clang__ 20 | #define NO_UNIQUE_ADDRESS 21 | #else 22 | #define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] 23 | #endif 24 | #else 25 | #define EMPTY_BASES 26 | #define NO_UNIQUE_ADDRESS [[no_unique_address]] 27 | #endif 28 | 29 | namespace std { 30 | struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) _Aligned_block { 31 | unsigned char _Pad[__STDCPP_DEFAULT_NEW_ALIGNMENT__]; 32 | }; 33 | 34 | template 35 | using _Rebind = typename allocator_traits<_Alloc>::template rebind_alloc<_Aligned_block>; 36 | 37 | template 38 | concept _Has_real_pointers = 39 | same_as<_Alloc, void> || is_pointer_v::pointer>; 40 | 41 | template 42 | class _Promise_allocator { // statically specified allocator type 43 | private: 44 | using _Alloc = _Rebind<_Allocator>; 45 | 46 | static void* _Allocate(_Alloc _Al, const size_t _Size) { 47 | if constexpr (default_initializable< 48 | _Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { 49 | // do not store stateless allocator 50 | const size_t _Count = (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); 51 | return _Al.allocate(_Count); 52 | } else { 53 | // store stateful allocator 54 | static constexpr size_t _Align = 55 | (::std::max)(alignof(_Alloc), sizeof(_Aligned_block)); 56 | const size_t _Count = 57 | (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); 58 | void* const _Ptr = _Al.allocate(_Count); 59 | const auto _Al_address = 60 | (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) 61 | & ~(alignof(_Alloc) - 1); 62 | ::new (reinterpret_cast(_Al_address)) _Alloc(::std::move(_Al)); 63 | return _Ptr; 64 | } 65 | } 66 | 67 | public: 68 | static void* operator new(const size_t _Size) requires default_initializable<_Alloc> { 69 | return _Allocate(_Alloc{}, _Size); 70 | } 71 | 72 | template 73 | requires convertible_to 74 | static void* operator new( 75 | const size_t _Size, allocator_arg_t, const _Alloc2& _Al, const _Args&...) { 76 | return _Allocate(static_cast<_Alloc>(static_cast<_Allocator>(_Al)), _Size); 77 | } 78 | 79 | template 80 | requires convertible_to 81 | static void* operator new(const size_t _Size, const _This&, allocator_arg_t, 82 | const _Alloc2& _Al, const _Args&...) { 83 | return _Allocate(static_cast<_Alloc>(static_cast<_Allocator>(_Al)), _Size); 84 | } 85 | 86 | static void operator delete(void* const _Ptr, const size_t _Size) noexcept { 87 | if constexpr (default_initializable< 88 | _Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { 89 | // make stateless allocator 90 | _Alloc _Al{}; 91 | const size_t _Count = (_Size + sizeof(_Aligned_block) - 1) / sizeof(_Aligned_block); 92 | _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); 93 | } else { 94 | // retrieve stateful allocator 95 | const auto _Al_address = 96 | (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) 97 | & ~(alignof(_Alloc) - 1); 98 | auto& _Stored_al = *reinterpret_cast<_Alloc*>(_Al_address); 99 | _Alloc _Al{::std::move(_Stored_al)}; 100 | _Stored_al.~_Alloc(); 101 | 102 | static constexpr size_t _Align = 103 | (::std::max)(alignof(_Alloc), sizeof(_Aligned_block)); 104 | const size_t _Count = 105 | (_Size + sizeof(_Alloc) + _Align - 1) / sizeof(_Aligned_block); 106 | _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); 107 | } 108 | } 109 | }; 110 | 111 | template <> 112 | class _Promise_allocator { // type-erased allocator 113 | private: 114 | using _Dealloc_fn = void (*)(void*, size_t); 115 | 116 | template 117 | static void* _Allocate(const _ProtoAlloc& _Proto, size_t _Size) { 118 | using _Alloc = _Rebind<_ProtoAlloc>; 119 | auto _Al = static_cast<_Alloc>(_Proto); 120 | 121 | if constexpr (default_initializable< 122 | _Alloc> && allocator_traits<_Alloc>::is_always_equal::value) { 123 | // don't store stateless allocator 124 | const _Dealloc_fn _Dealloc = [](void* const _Ptr, const size_t _Size) { 125 | _Alloc _Al{}; 126 | const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Aligned_block) - 1) 127 | / sizeof(_Aligned_block); 128 | _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); 129 | }; 130 | 131 | const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Aligned_block) - 1) 132 | / sizeof(_Aligned_block); 133 | void* const _Ptr = _Al.allocate(_Count); 134 | ::memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); 135 | return _Ptr; 136 | } else { 137 | // store stateful allocator 138 | static constexpr size_t _Align = 139 | (::std::max)(alignof(_Alloc), sizeof(_Aligned_block)); 140 | 141 | const _Dealloc_fn _Dealloc = [](void* const _Ptr, size_t _Size) { 142 | _Size += sizeof(_Dealloc_fn); 143 | const auto _Al_address = 144 | (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) 145 | & ~(alignof(_Alloc) - 1); 146 | auto& _Stored_al = *reinterpret_cast(_Al_address); 147 | _Alloc _Al{::std::move(_Stored_al)}; 148 | _Stored_al.~_Alloc(); 149 | 150 | const size_t _Count = 151 | (_Size + sizeof(_Al) + _Align - 1) / sizeof(_Aligned_block); 152 | _Al.deallocate(static_cast<_Aligned_block*>(_Ptr), _Count); 153 | }; 154 | 155 | const size_t _Count = (_Size + sizeof(_Dealloc_fn) + sizeof(_Al) + _Align - 1) 156 | / sizeof(_Aligned_block); 157 | void* const _Ptr = _Al.allocate(_Count); 158 | ::memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc)); 159 | _Size += sizeof(_Dealloc_fn); 160 | const auto _Al_address = 161 | (reinterpret_cast(_Ptr) + _Size + alignof(_Alloc) - 1) 162 | & ~(alignof(_Alloc) - 1); 163 | ::new (reinterpret_cast(_Al_address)) _Alloc{::std::move(_Al)}; 164 | return _Ptr; 165 | } 166 | } 167 | 168 | public: 169 | static void* operator new(const size_t _Size) { // default: new/delete 170 | void* const _Ptr = ::operator new[](_Size + sizeof(_Dealloc_fn)); 171 | const _Dealloc_fn _Dealloc = [](void* const _Ptr, const size_t _Size) { 172 | ::operator delete[](_Ptr, _Size + sizeof(_Dealloc_fn)); 173 | }; 174 | ::memcpy(static_cast(_Ptr) + _Size, &_Dealloc, sizeof(_Dealloc_fn)); 175 | return _Ptr; 176 | } 177 | 178 | template 179 | static void* operator new( 180 | const size_t _Size, allocator_arg_t, const _Alloc& _Al, const _Args&...) { 181 | static_assert( 182 | _Has_real_pointers<_Alloc>, "coroutine allocators must use true pointers"); 183 | return _Allocate(_Al, _Size); 184 | } 185 | 186 | template 187 | static void* operator new( 188 | const size_t _Size, const _This&, allocator_arg_t, const _Alloc& _Al, const _Args&...) { 189 | static_assert( 190 | _Has_real_pointers<_Alloc>, "coroutine allocators must use true pointers"); 191 | return _Allocate(_Al, _Size); 192 | } 193 | 194 | static void operator delete(void* const _Ptr, const size_t _Size) noexcept { 195 | _Dealloc_fn _Dealloc; 196 | ::memcpy(&_Dealloc, static_cast(_Ptr) + _Size, sizeof(_Dealloc_fn)); 197 | _Dealloc(_Ptr, _Size); 198 | } 199 | }; 200 | 201 | namespace ranges { 202 | template > 203 | struct elements_of { 204 | NO_UNIQUE_ADDRESS _Rng range; 205 | NO_UNIQUE_ADDRESS _Alloc allocator{}; 206 | }; 207 | 208 | template > 209 | elements_of(_Rng&&, _Alloc = {}) -> elements_of<_Rng&&, _Alloc>; 210 | } // namespace ranges 211 | 212 | template 213 | class generator; 214 | 215 | template 216 | using _Gen_value_t = conditional_t, remove_cvref_t<_Rty>, _Vty>; 217 | template 218 | using _Gen_reference_t = conditional_t, _Rty&&, _Rty>; 219 | template 220 | using _Gen_yield_t = conditional_t, _Ref, const _Ref&>; 221 | 222 | template 223 | class _Gen_promise_base { 224 | public: 225 | static_assert(is_reference_v<_Yielded>); 226 | 227 | /* [[nodiscard]] */ suspend_always initial_suspend() noexcept { 228 | return {}; 229 | } 230 | 231 | [[nodiscard]] auto final_suspend() noexcept { 232 | return _Final_awaiter{}; 233 | } 234 | 235 | [[nodiscard]] suspend_always yield_value(_Yielded _Val) noexcept { 236 | _Ptr = ::std::addressof(_Val); 237 | return {}; 238 | } 239 | 240 | // clang-format off 241 | [[nodiscard]] auto yield_value(const remove_reference_t<_Yielded>& _Val) 242 | noexcept(is_nothrow_constructible_v, const remove_reference_t<_Yielded>&>) 243 | requires (is_rvalue_reference_v<_Yielded> && 244 | constructible_from, const remove_reference_t<_Yielded>&>) { 245 | // clang-format on 246 | return _Element_awaiter{_Val}; 247 | } 248 | 249 | // clang-format off 250 | template 251 | requires same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded> 252 | [[nodiscard]] auto yield_value( 253 | ::std::ranges::elements_of&&, _Unused> _Elem) noexcept { 254 | // clang-format on 255 | return _Nested_awaitable<_Rty, _Vty, _Alloc>{std::move(_Elem.range)}; 256 | } 257 | 258 | // clang-format off 259 | template <::std::ranges::input_range _Rng, class _Alloc> 260 | requires convertible_to<::std::ranges::range_reference_t<_Rng>, _Yielded> 261 | [[nodiscard]] auto yield_value(::std::ranges::elements_of<_Rng, _Alloc> _Elem) noexcept { 262 | // clang-format on 263 | using _Vty = ::std::ranges::range_value_t<_Rng>; 264 | return _Nested_awaitable<_Yielded, _Vty, _Alloc>{ 265 | [](allocator_arg_t, _Alloc, ::std::ranges::iterator_t<_Rng> _It, 266 | const ::std::ranges::sentinel_t<_Rng> _Se) 267 | -> generator<_Yielded, _Vty, _Alloc> { 268 | for (; _It != _Se; ++_It) { 269 | co_yield static_cast<_Yielded>(*_It); 270 | } 271 | }(allocator_arg, _Elem.allocator, ::std::ranges::begin(_Elem.range), 272 | ::std::ranges::end(_Elem.range))}; 273 | } 274 | 275 | void await_transform() = delete; 276 | 277 | void return_void() noexcept {} 278 | 279 | void unhandled_exception() { 280 | if (_Info) { 281 | _Info->_Except = ::std::current_exception(); 282 | } else { 283 | throw; 284 | } 285 | } 286 | 287 | private: 288 | struct _Element_awaiter { 289 | remove_cvref_t<_Yielded> _Val; 290 | 291 | [[nodiscard]] constexpr bool await_ready() const noexcept { 292 | return false; 293 | } 294 | 295 | template 296 | constexpr void await_suspend(coroutine_handle<_Promise> _Handle) noexcept { 297 | #ifdef __cpp_lib_is_pointer_interconvertible 298 | static_assert(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); 299 | #endif // __cpp_lib_is_pointer_interconvertible 300 | 301 | _Gen_promise_base& _Current = _Handle.promise(); 302 | _Current._Ptr = ::std::addressof(_Val); 303 | } 304 | 305 | constexpr void await_resume() const noexcept {} 306 | }; 307 | 308 | struct _Nest_info { 309 | exception_ptr _Except; 310 | coroutine_handle<_Gen_promise_base> _Parent; 311 | coroutine_handle<_Gen_promise_base> _Root; 312 | }; 313 | 314 | struct _Final_awaiter { 315 | [[nodiscard]] bool await_ready() noexcept { 316 | return false; 317 | } 318 | 319 | template 320 | [[nodiscard]] coroutine_handle<> await_suspend( 321 | coroutine_handle<_Promise> _Handle) noexcept { 322 | #ifdef __cpp_lib_is_pointer_interconvertible 323 | static_assert(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); 324 | #endif // __cpp_lib_is_pointer_interconvertible 325 | 326 | _Gen_promise_base& _Current = _Handle.promise(); 327 | if (!_Current._Info) { 328 | return ::std::noop_coroutine(); 329 | } 330 | 331 | coroutine_handle<_Gen_promise_base> _Cont = _Current._Info->_Parent; 332 | _Current._Info->_Root.promise()._Top = _Cont; 333 | _Current._Info = nullptr; 334 | return _Cont; 335 | } 336 | 337 | void await_resume() noexcept {} 338 | }; 339 | 340 | template 341 | struct _Nested_awaitable { 342 | static_assert(same_as<_Gen_yield_t<_Gen_reference_t<_Rty, _Vty>>, _Yielded>); 343 | 344 | _Nest_info _Nested; 345 | generator<_Rty, _Vty, _Alloc> _Gen; 346 | 347 | explicit _Nested_awaitable(generator<_Rty, _Vty, _Alloc>&& _Gen_) noexcept 348 | : _Gen(::std::move(_Gen_)) {} 349 | 350 | [[nodiscard]] bool await_ready() noexcept { 351 | return !_Gen._Coro; 352 | } 353 | 354 | template 355 | [[nodiscard]] coroutine_handle<_Gen_promise_base> await_suspend( 356 | coroutine_handle<_Promise> _Current) noexcept { 357 | #ifdef __cpp_lib_is_pointer_interconvertible 358 | static_assert(is_pointer_interconvertible_base_of_v<_Gen_promise_base, _Promise>); 359 | #endif // __cpp_lib_is_pointer_interconvertible 360 | auto _Target = 361 | coroutine_handle<_Gen_promise_base>::from_address(_Gen._Coro.address()); 362 | _Nested._Parent = 363 | coroutine_handle<_Gen_promise_base>::from_address(_Current.address()); 364 | _Gen_promise_base& _Parent_promise = _Nested._Parent.promise(); 365 | if (_Parent_promise._Info) { 366 | _Nested._Root = _Parent_promise._Info->_Root; 367 | } else { 368 | _Nested._Root = _Nested._Parent; 369 | } 370 | _Nested._Root.promise()._Top = _Target; 371 | _Target.promise()._Info = ::std::addressof(_Nested); 372 | return _Target; 373 | } 374 | 375 | void await_resume() { 376 | if (_Nested._Except) { 377 | ::std::rethrow_exception(::std::move(_Nested._Except)); 378 | } 379 | } 380 | }; 381 | 382 | template 383 | friend class _Gen_iter; 384 | 385 | // _Top and _Info are mutually exclusive, and could potentially be merged. 386 | coroutine_handle<_Gen_promise_base> _Top = 387 | coroutine_handle<_Gen_promise_base>::from_promise(*this); 388 | add_pointer_t<_Yielded> _Ptr = nullptr; 389 | _Nest_info* _Info = nullptr; 390 | }; 391 | 392 | struct _Gen_secret_tag {}; 393 | 394 | template 395 | class _Gen_iter { 396 | public: 397 | using value_type = _Value; 398 | using difference_type = ptrdiff_t; 399 | 400 | _Gen_iter(_Gen_iter&& _That) noexcept : _Coro{::std::exchange(_That._Coro, {})} {} 401 | 402 | _Gen_iter& operator=(_Gen_iter&& _That) noexcept { 403 | _Coro = ::std::exchange(_That._Coro, {}); 404 | return *this; 405 | } 406 | 407 | [[nodiscard]] _Ref operator*() const noexcept { 408 | assert(!_Coro.done() && "Can't dereference generator end iterator"); 409 | return static_cast<_Ref>(*_Coro.promise()._Top.promise()._Ptr); 410 | } 411 | 412 | _Gen_iter& operator++() { 413 | assert(!_Coro.done() && "Can't increment generator end iterator"); 414 | _Coro.promise()._Top.resume(); 415 | return *this; 416 | } 417 | 418 | void operator++(int) { 419 | ++*this; 420 | } 421 | 422 | [[nodiscard]] bool operator==(default_sentinel_t) const noexcept { 423 | return _Coro.done(); 424 | } 425 | 426 | private: 427 | template 428 | friend class generator; 429 | 430 | explicit _Gen_iter(_Gen_secret_tag, 431 | coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro_) noexcept 432 | : _Coro{_Coro_} {} 433 | 434 | coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>> _Coro; 435 | }; 436 | 437 | template 438 | class generator : public ranges::view_interface> { 439 | private: 440 | using _Value = _Gen_value_t<_Rty, _Vty>; 441 | static_assert(same_as, _Value> && is_object_v<_Value>, 442 | "generator's value type must be a cv-unqualified object type"); 443 | 444 | // clang-format off 445 | using _Ref = _Gen_reference_t<_Rty, _Vty>; 446 | static_assert(is_reference_v<_Ref> 447 | || (is_object_v<_Ref> && same_as, _Ref> && copy_constructible<_Ref>), 448 | "generator's second argument must be a reference type or a cv-unqualified " 449 | "copy-constructible object type"); 450 | 451 | using _RRef = conditional_t, remove_reference_t<_Ref>&&, _Ref>; 452 | 453 | static_assert(common_reference_with<_Ref&&, _Value&> && common_reference_with<_Ref&&, _RRef&&> 454 | && common_reference_with<_RRef&&, const _Value&>, 455 | "an iterator with the selected value and reference types cannot model indirectly_readable"); 456 | // clang-format on 457 | 458 | // work around error C2825: '_Alloc': must be a class or namespace when followed by '::' 459 | // static_assert(_Has_real_pointers<_Alloc>, "generator allocators must use true pointers"); 460 | 461 | friend _Gen_promise_base<_Gen_yield_t<_Ref>>; 462 | 463 | public: 464 | struct EMPTY_BASES promise_type : _Promise_allocator<_Alloc>, 465 | _Gen_promise_base<_Gen_yield_t<_Ref>> { 466 | [[nodiscard]] generator get_return_object() noexcept { 467 | return generator{ 468 | _Gen_secret_tag{}, coroutine_handle::from_promise(*this)}; 469 | } 470 | }; 471 | static_assert(is_standard_layout_v); 472 | #ifdef __cpp_lib_is_pointer_interconvertible 473 | static_assert(is_pointer_interconvertible_base_of_v<_Gen_promise_base<_Gen_yield_t<_Ref>>, 474 | promise_type>); 475 | #endif // __cpp_lib_is_pointer_interconvertible 476 | 477 | generator(generator&& _That) noexcept : _Coro(::std::exchange(_That._Coro, {})) {} 478 | 479 | ~generator() { 480 | if (_Coro) { 481 | _Coro.destroy(); 482 | } 483 | } 484 | 485 | generator& operator=(generator _That) noexcept { 486 | ::std::swap(_Coro, _That._Coro); 487 | return *this; 488 | } 489 | 490 | [[nodiscard]] _Gen_iter<_Value, _Ref> begin() { 491 | // Pre: _Coro is suspended at its initial suspend point 492 | assert(_Coro && "Can't call begin on moved-from generator"); 493 | _Coro.resume(); 494 | return _Gen_iter<_Value, _Ref>{_Gen_secret_tag{}, 495 | coroutine_handle<_Gen_promise_base<_Gen_yield_t<_Ref>>>::from_address( 496 | _Coro.address())}; 497 | } 498 | 499 | [[nodiscard]] default_sentinel_t end() const noexcept { 500 | return default_sentinel; 501 | } 502 | 503 | private: 504 | coroutine_handle _Coro = nullptr; 505 | 506 | explicit generator(_Gen_secret_tag, coroutine_handle _Coro_) noexcept 507 | : _Coro(_Coro_) {} 508 | }; 509 | } // namespace std 510 | -------------------------------------------------------------------------------- /Demo-App/generator.ixx: -------------------------------------------------------------------------------- 1 | module; 2 | #include "generator.hpp" // Reference implementation of std::generator proposal P2502R2 3 | 4 | export module generator; 5 | 6 | // export everything from this namespace segment 7 | export namespace std { 8 | using generator = ::std::generator; 9 | namespace ranges { 10 | using elements_of = ::std::ranges::elements_of; 11 | } 12 | } // namespace std 13 | -------------------------------------------------------------------------------- /Demo-App/gui.cpp: -------------------------------------------------------------------------------- 1 | module gui; 2 | 3 | namespace gui { 4 | 5 | static const auto initializedSDL = SDL_Init(SDL_INIT_VIDEO); 6 | static constexpr auto TextureFormat = SDL_PIXELFORMAT_ARGB8888; 7 | 8 | static constexpr bool successful(int Code) { 9 | return Code == 0; 10 | } 11 | 12 | static auto centeredBox(tDimensions Dimensions, 13 | int Monitor = SDL_GetNumVideoDisplays()) noexcept { 14 | struct { 15 | int x = SDL_WINDOWPOS_CENTERED; 16 | int y = SDL_WINDOWPOS_CENTERED; 17 | int Width; 18 | int Height; 19 | } Box{ .Width = Dimensions.Width, .Height = Dimensions.Height }; 20 | 21 | if (SDL_Rect Display; 22 | Monitor > 0 and successful(SDL_GetDisplayBounds(Monitor - 1, &Display))) { 23 | Box.Width = std::min(Display.w, Box.Width); 24 | Box.Height = std::min(Display.h, Box.Height); 25 | Box.x = Display.x + (Display.w - Box.Width) / 2; 26 | Box.y = Display.y + (Display.h - Box.Height) / 2; 27 | } 28 | return Box; 29 | } 30 | 31 | FancyWindow::FancyWindow(tDimensions Dimensions) noexcept { 32 | const auto Viewport = centeredBox(Dimensions); 33 | 34 | Window_ = { "Look at me!", // clang-format off 35 | Viewport.x, Viewport.y, Viewport.Width, Viewport.Height, // clang-format on 36 | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN }; 37 | Renderer_ = { Window_, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC }; 38 | 39 | SDL_SetWindowMinimumSize(Window_, Viewport.Width, Viewport.Height); 40 | SDL_RenderSetLogicalSize(Renderer_, Viewport.Width, Viewport.Height); 41 | SDL_RenderSetIntegerScale(Renderer_, SDL_TRUE); 42 | SDL_SetRenderDrawColor(Renderer_, 240, 240, 240, 240); 43 | } 44 | 45 | void FancyWindow::updateFrom(const video::FrameHeader & Header) noexcept { 46 | if (not Header.isFirstFrame()) 47 | return; 48 | 49 | if (Header.hasNoPixels()) { 50 | SDL_HideWindow(Window_); 51 | Texture_ = {}; 52 | } else { 53 | Width_ = Header.Width_; 54 | Height_ = Header.Height_; 55 | PixelsPitch_ = Header.LinePitch_; 56 | SourceFormat_ = Header.Format_ == std::to_underlying(video::PixelFormat::RGBA) 57 | ? SDL_PIXELFORMAT_ABGR8888 58 | : SDL_PIXELFORMAT_ARGB8888; 59 | Texture_ = sdl::Texture(Renderer_, TextureFormat, SDL_TEXTUREACCESS_STREAMING, 60 | Width_, Height_); 61 | SDL_SetWindowMinimumSize(Window_, Width_, Height_); 62 | SDL_RenderSetLogicalSize(Renderer_, Width_, Height_); 63 | SDL_ShowWindow(Window_); 64 | } 65 | } 66 | 67 | void FancyWindow::present(video::tPixels Pixels) noexcept { 68 | void * TextureData; 69 | int TexturePitch; 70 | 71 | SDL_RenderClear(Renderer_); 72 | if (successful(SDL_LockTexture(Texture_, nullptr, &TextureData, &TexturePitch))) { 73 | SDL_ConvertPixels(Width_, Height_, SourceFormat_, Pixels.data(), PixelsPitch_, 74 | TextureFormat, TextureData, TexturePitch); 75 | SDL_UnlockTexture(Texture_); 76 | SDL_RenderCopy(Renderer_, Texture_, nullptr, nullptr); 77 | } 78 | SDL_RenderPresent(Renderer_); 79 | } 80 | 81 | bool isAlive() noexcept { 82 | SDL_Event event; 83 | while (SDL_PollEvent(&event)) { 84 | if (event.type == SDL_QUIT) 85 | return false; 86 | } 87 | return true; 88 | } 89 | 90 | } // namespace gui 91 | -------------------------------------------------------------------------------- /Demo-App/gui.ixx: -------------------------------------------------------------------------------- 1 | module; 2 | #include "c_resource.hpp" 3 | 4 | export module gui; 5 | import std; 6 | 7 | import sdl; 8 | import video; 9 | 10 | // wrap the SDL (Simple Directmedia Layer https://www.libsdl.org/) C API types 11 | // and their assorted functions 12 | 13 | namespace sdl { 14 | using Window = stdex::c_resource; 15 | using Renderer = stdex::c_resource; 16 | using Texture = stdex::c_resource; 17 | } // namespace sdl 18 | 19 | export namespace gui { 20 | struct tDimensions { 21 | uint16_t Width; 22 | uint16_t Height; 23 | }; 24 | 25 | // the most minimal GUI 26 | // capable of showing a frame with some decor for user interaction 27 | // renders the video frames 28 | struct FancyWindow { 29 | explicit FancyWindow(tDimensions) noexcept; 30 | 31 | void updateFrom(const video::FrameHeader & Header) noexcept; 32 | void present(video::tPixels Pixels) noexcept; 33 | 34 | private: 35 | sdl::Window Window_; 36 | sdl::Renderer Renderer_; 37 | sdl::Texture Texture_; 38 | int Width_; 39 | int Height_; 40 | int PixelsPitch_; 41 | int SourceFormat_; 42 | }; 43 | 44 | bool isAlive() noexcept; 45 | 46 | } // namespace gui 47 | -------------------------------------------------------------------------------- /Demo-App/main.cpp: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | The server 3 | 4 | - waits for clients to connect at anyone of a list of given endpoints 5 | - when a client connects, observes a given directory for all files in there, 6 | repeating this endlessly 7 | - filters all GIF files which contain a video 8 | - decodes each video file into individual video frames 9 | - sends each frame at the correct time to the client 10 | - sends filler frames if there happen to be no GIF files to process 11 | 12 | The client 13 | 14 | - tries to connect to anyone of a list of given server endpoints 15 | - receives video frames from the network connection 16 | - presents the video frames in a reasonable manner in a GUI window 17 | 18 | The application 19 | 20 | - watches all inputs that the user can interact with for the desire to end 21 | the application 22 | - handles timeouts and errors properly and performs a clean shutdown if needed 23 | ==============================================================================*/ 24 | 25 | import std; 26 | 27 | import asio; 28 | import executor; 29 | import gui; 30 | import net; 31 | import the.whole.caboodle; 32 | 33 | import client; 34 | import events; 35 | import server; 36 | 37 | using namespace std::chrono_literals; 38 | 39 | static constexpr auto ServerPort = net::tPort{ 34567 }; 40 | static constexpr auto ResolveTimeBudget = 1s; 41 | 42 | int main(int argc, char * argv[]) { 43 | auto [MediaDirectory, ServerName] = caboodle::getOptions(argc, argv); 44 | if (MediaDirectory.empty()) 45 | return -2; 46 | const auto ServerEndpoints = 47 | net::resolveHostEndpoints(ServerName, ServerPort, ResolveTimeBudget); 48 | if (ServerEndpoints.empty()) 49 | return -3; 50 | 51 | asio::io_context ExecutionContext; // we have executors at home 52 | std::stop_source Stop; // the mother of all stops 53 | const auto schedule = executor::makeScheduler(ExecutionContext, Stop); 54 | 55 | const auto Listening = 56 | schedule(server::serve, ServerEndpoints, std::move(MediaDirectory)); 57 | if (not Listening) 58 | return -4; 59 | 60 | schedule(client::showVideos, gui::FancyWindow({ .Width = 1280, .Height = 1024 }), 61 | ServerEndpoints); 62 | schedule(handleEvents::fromTerminal); 63 | schedule(handleEvents::fromGUI); 64 | 65 | ExecutionContext.run(); 66 | } 67 | -------------------------------------------------------------------------------- /Demo-App/net.cpp: -------------------------------------------------------------------------------- 1 | module net; 2 | import std; 3 | 4 | import asio; 5 | 6 | // the lowest-level networking routines with support for cancellation and timeouts 7 | 8 | namespace net { 9 | using namespace asio; 10 | 11 | // precondition: not Data.empty() 12 | auto sendTo(tSocket & Socket, tTimer & Timer, tConstBuffers Data) 13 | -> awaitable { 14 | co_return flatten(co_await (async_write(Socket, Data) || Timer.async_wait())); 15 | } 16 | 17 | // precondition: not Space.empty() 18 | auto receiveFrom(tSocket & Socket, tTimer & Timer, tByteSpan Space) 19 | -> awaitable { 20 | co_return flatten(co_await (async_read(Socket, buffer(Space)) || Timer.async_wait())); 21 | } 22 | 23 | // precondition: not Endpoints.empty() 24 | auto connectTo(tEndpoints Endpoints, tTimer & Timer) -> awaitable { 25 | tSocket Socket(Timer.get_executor()); 26 | co_return replace( 27 | flatten(co_await (async_connect(Socket, Endpoints) || Timer.async_wait())), 28 | std::move(Socket)); 29 | } 30 | 31 | auto expired(tTimer & Timer) noexcept -> asio::awaitable { 32 | const auto [Error] = co_await Timer.async_wait(); 33 | co_return not Error; 34 | } 35 | 36 | void close(tSocket & Socket) noexcept { 37 | std::error_code Error; 38 | Socket.shutdown(tSocket::shutdown_both, Error); 39 | Socket.close(Error); 40 | } 41 | 42 | using namespace std::string_view_literals; 43 | static constexpr auto Local = "localhost"sv; 44 | 45 | // precondition: TimeBudget > 0 46 | auto resolveHostEndpoints(std::string_view HostName, tPort Port, 47 | std::chrono::milliseconds TimeBudget) 48 | -> std::vector { 49 | using tResolver = asio::ip::tcp::resolver; 50 | auto Flags = tResolver::numeric_service; 51 | if (HostName.empty() || HostName == Local) { 52 | Flags |= tResolver::passive; 53 | HostName = Local; 54 | } 55 | 56 | std::vector Endpoints; 57 | const auto toEndpoints = [&](const auto &, auto Resolved) { 58 | Endpoints.reserve(Resolved.size()); 59 | for (const auto & Element : Resolved) 60 | Endpoints.push_back(Element.endpoint()); 61 | }; 62 | 63 | asio::io_context Executor; 64 | tResolver Resolver(Executor); 65 | Resolver.async_resolve(HostName, std::to_string(Port), Flags, toEndpoints); 66 | Executor.run_for(TimeBudget); 67 | return Endpoints; 68 | } 69 | 70 | } // namespace net 71 | -------------------------------------------------------------------------------- /Demo-App/net.ixx: -------------------------------------------------------------------------------- 1 | export module net; 2 | import std; 3 | 4 | import asio; 5 | 6 | // Convenience types and functions to deal with the networking part of the 7 | // Asio library https://think-async.com/Asio/ 8 | 9 | // The Asio library is also the reference implementation of the 10 | // Networking TS (ISO/IEC TS 19216:2018, C++ Extensions for Library Fundamentals) 11 | // Draft https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf 12 | // plus P0958, P1322, P1943, P2444 13 | // plus executors as decribed in P0443, P1348, and P1393 14 | 15 | namespace aex = asio::experimental; 16 | namespace net { 17 | 18 | // basic networking types 19 | 20 | export { 21 | // customize the regular Asio types and functions such that they can be 22 | // - conveniently used in await expressions 23 | // - composed into higher level, augmented operations 24 | 25 | template 26 | using tResult = std::tuple; 27 | using use_await = asio::as_tuple_t>; 28 | using tSocket = use_await::as_default_on_t; 29 | using tAcceptor = use_await::as_default_on_t; 30 | using tTimer = use_await::as_default_on_t; 31 | 32 | using tEndpoint = asio::ip::tcp::endpoint; 33 | using tEndpoints = std::span; 34 | using tByteSpan = std::span; 35 | using tConstByteSpan = std::span; 36 | 37 | template 38 | using tSendBuffers = std::array; 39 | using tConstBuffers = std::span; 40 | 41 | enum tPort : uint16_t {}; 42 | 43 | // the network layer uses std::expected as return types 44 | 45 | template 46 | using tExpected = std::expected; 47 | using tExpectSize = tExpected; 48 | using tExpectSocket = tExpected; 49 | } // export 50 | 51 | // transform the 'variant' return type from asio operator|| into an 'expected' 52 | // as simply as possible to scare away no one. No TMP required here! 53 | 54 | template 55 | constexpr auto _map(tResult && Tuple) -> net::tExpected { 56 | const auto & Error = std::get(Tuple); 57 | if constexpr (sizeof...(Ts) == 0) 58 | return std::unexpected{ Error }; 59 | else if (Error) 60 | return std::unexpected{ Error }; 61 | else 62 | return std::get(std::move(Tuple)); 63 | } 64 | 65 | export { 66 | using aex::awaitable_operators::operator||; 67 | 68 | template 69 | constexpr auto flatten(std::variant, tResult> && Variant) { 70 | using net::_map; 71 | using tReturn = std::type_identity::type; 72 | return std::visit( 73 | [](auto && Tuple) { 74 | return _map(std::move(Tuple)); 75 | }, 76 | std::move(Variant)); 77 | } 78 | 79 | template 80 | auto replace(net::tExpected && Input, Out && Replacement)->net::tExpected { 81 | return std::move(Input).transform([&](In &&) { 82 | return std::forward(Replacement); 83 | }); 84 | } 85 | 86 | auto asBytes(const auto & Object) noexcept -> asio::const_buffer { 87 | const auto Bytes = std::as_bytes(std::span{ &Object, 1 }); 88 | return { Bytes.data(), Bytes.size() }; 89 | } 90 | 91 | auto sendTo(tSocket & Socket, tTimer & Timer, tConstBuffers DataToSend) 92 | ->asio::awaitable; 93 | auto receiveFrom(tSocket & Socket, tTimer & Timer, tByteSpan SpaceToFill) 94 | ->asio::awaitable; 95 | auto connectTo(tEndpoints EndpointsToTry, tTimer & Timer) 96 | ->asio::awaitable; 97 | auto expired(tTimer & Timer) noexcept -> asio::awaitable; 98 | 99 | void close(tSocket & Socket) noexcept; 100 | auto resolveHostEndpoints(std::string_view HostName, tPort Port, 101 | std::chrono::milliseconds TimeBudget) 102 | ->std::vector; 103 | } // export 104 | } // namespace net 105 | -------------------------------------------------------------------------------- /Demo-App/server.ixx: -------------------------------------------------------------------------------- 1 | export module server; 2 | import std; 3 | 4 | import asio; 5 | import net; 6 | import video; 7 | import executor; 8 | 9 | using namespace std::chrono_literals; 10 | namespace fs = std::filesystem; 11 | 12 | namespace server { 13 | static constexpr auto SendTimeBudget = 100ms; 14 | 15 | // create a closure with a call operator that returns an awaitable taylored to each 16 | // given frame. 17 | // the awaitable can then be co_awaited and execution of the awaiter will resume at 18 | // exactly that time that the given frame is supposed to be sent out. 19 | 20 | [[nodiscard]] auto makeStartingGate(net::tTimer & Timer) { 21 | using std::chrono::steady_clock; 22 | auto StartTime = steady_clock::now(); 23 | auto Timestamp = video::FrameHeader::µSeconds{ 0 }; 24 | 25 | return [=, &Timer](const video::Frame & Frame) mutable { 26 | const auto & Header = Frame.Header_; 27 | const auto DueTime = 28 | StartTime + (Header.isFiller() ? Timestamp : Header.Timestamp_); 29 | if (Header.isFirstFrame()) 30 | StartTime = steady_clock::now(); 31 | Timestamp = Header.Timestamp_; 32 | Timer.expires_at(DueTime); 33 | return Timer.async_wait(); 34 | }; 35 | } 36 | 37 | // the connection is implemented as an independent coroutine. 38 | // it will be brought down by internal events or from the outside using a 39 | // stop signal. 40 | 41 | [[nodiscard]] auto streamVideos(net::tSocket Socket, fs::path Source) 42 | -> asio::awaitable { 43 | net::tTimer Timer(Socket.get_executor()); 44 | const auto WatchDog = executor::abort(Socket, Timer); 45 | 46 | auto DueTime = makeStartingGate(Timer); 47 | for (const auto Frame : video::makeFrames(std::move(Source))) { 48 | co_await DueTime(Frame); 49 | 50 | net::tSendBuffers<2> Buffers{ net::asBytes(Frame.Header_), 51 | asio::buffer(Frame.Pixels_) }; 52 | Timer.expires_after(SendTimeBudget); 53 | if (Frame.TotalSize() != co_await net::sendTo(Socket, Timer, Buffers)) 54 | break; 55 | } 56 | } 57 | 58 | // the tcp acceptor is a coroutine. 59 | // it spawns new, independent coroutines on connect. 60 | 61 | [[nodiscard]] auto acceptConnections(net::tAcceptor Acceptor, const fs::path Source) 62 | -> asio::awaitable { 63 | const auto WatchDog = executor::abort(Acceptor); 64 | 65 | while (Acceptor.is_open()) { 66 | auto [Error, Socket] = co_await Acceptor.async_accept(); 67 | if (not Error and Socket.is_open()) 68 | executor::commission(Acceptor.get_executor(), streamVideos, std::move(Socket), 69 | Source); 70 | } 71 | } 72 | 73 | // start serving a list of given endpoints. 74 | // each endpoint is served by an independent coroutine. 75 | 76 | export auto serve(asio::io_context & Context, net::tEndpoints Endpoints, 77 | const fs::path Source) -> net::tExpectSize { 78 | std::size_t NumberOfAcceptors = 0; 79 | auto Error = std::make_error_code(std::errc::function_not_supported); 80 | 81 | for (const auto & Endpoint : Endpoints) { 82 | try { 83 | executor::commission(Context, acceptConnections, 84 | net::tAcceptor{ Context, Endpoint }, Source); 85 | std::println("accept connections at {}", Endpoint.address().to_string()); 86 | ++NumberOfAcceptors; 87 | } catch (const std::system_error & Ex) { 88 | Error = Ex.code(); 89 | } 90 | } 91 | if (NumberOfAcceptors == 0) 92 | return std::unexpected{ Error }; 93 | return NumberOfAcceptors; 94 | } 95 | } // namespace server 96 | -------------------------------------------------------------------------------- /Demo-App/video.ixx: -------------------------------------------------------------------------------- 1 | export module video; 2 | 3 | export import :frame; 4 | export import :decoder; 5 | -------------------------------------------------------------------------------- /Demo-App/videodecoder.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include "c_resource.hpp" 3 | 4 | module video:decoder.pipeline; 5 | import std; 6 | 7 | import :frame; 8 | import the.whole.caboodle; 9 | import libav; 10 | 11 | namespace fs = std::filesystem; 12 | namespace rgs = std::ranges; 13 | namespace vws = rgs::views; 14 | 15 | // video frame generator 16 | // wrap the libav (a.k.a. FFmpeg https://ffmpeg.org/) C API types and their 17 | // assorted functions 18 | namespace libav { 19 | using Codec = 20 | stdex::c_resource; 21 | using File = 22 | stdex::c_resource; 23 | using tFrame = stdex::c_resource; 24 | using tPacket = stdex::c_resource; 25 | 26 | // frames and packets are reference-counted and always constructed non-empty 27 | struct [[nodiscard]] Frame : tFrame { 28 | Frame() 29 | : tFrame(constructed) {} 30 | auto dropReference() { return Frame::guard(*this); } 31 | }; 32 | struct [[nodiscard]] Packet : tPacket { 33 | Packet() 34 | : tPacket(constructed) {} 35 | auto dropReference() { return Packet::guard(*this); } 36 | }; 37 | } // namespace libav 38 | 39 | namespace video { 40 | 41 | // generate an endless stream of paths of the lastest contents of given Directory on each 42 | // iteration step. 43 | // the returned paths are empty if there are no directory contents. 44 | 45 | auto InfinitePathSource(fs::path Directory) -> std::generator { 46 | using fs::directory_options::skip_permission_denied; 47 | std::error_code Error; 48 | for (fs::directory_iterator atEnd{}, Iterator = atEnd; true;) { 49 | if (Iterator == atEnd or Iterator.increment(Error) == atEnd) 50 | Iterator = fs::directory_iterator{ Directory, skip_permission_denied, Error }; 51 | co_yield Error or Iterator == atEnd ? fs::path{} : Iterator->path(); 52 | } 53 | } 54 | 55 | static_assert(rgs::range); 56 | static_assert(rgs::viewable_range); 57 | 58 | static constexpr auto DetectStream = -1; 59 | static constexpr auto FirstStream = 0; 60 | static constexpr auto MainSubstream = 0; 61 | 62 | constexpr bool successful(int Code) { 63 | return Code >= 0; 64 | } 65 | 66 | constexpr bool atEndOfFile(int Code) { 67 | return Code == AVERROR_EOF; 68 | } 69 | 70 | auto acceptOnlyGIF(libav::File File) -> libav::File { 71 | const AVCodec * pCodec; 72 | if (av_find_best_stream(File, AVMEDIA_TYPE_VIDEO, DetectStream, -1, &pCodec, 0) != 73 | FirstStream or 74 | pCodec == nullptr or pCodec->id != AV_CODEC_ID_GIF) 75 | File = {}; 76 | return File; 77 | } 78 | 79 | auto tryOpenAsGIF(fs::path Path) -> libav::File { 80 | const auto Filename = caboodle::utf8Path(std::move(Path)); 81 | libav::File File; 82 | if (not Filename.empty() and 83 | successful(File.emplace(Filename.c_str(), nullptr, nullptr))) { 84 | File = acceptOnlyGIF(std::move(File)); 85 | } 86 | return File; 87 | } 88 | 89 | auto tryOpenVideoDecoder(libav::File File) -> std::tuple { 90 | if (not have(File)) 91 | return {}; 92 | 93 | const AVCodec * pCodec; 94 | avformat_find_stream_info(File, nullptr); 95 | av_find_best_stream(File, AVMEDIA_TYPE_VIDEO, FirstStream, -1, &pCodec, 0); 96 | if (File->duration <= 0) 97 | return {}; // refuse still images 98 | 99 | libav::Codec Decoder(pCodec); 100 | if (have(Decoder)) { 101 | avcodec_parameters_to_context(Decoder, File->streams[FirstStream]->codecpar); 102 | if (successful(avcodec_open2(Decoder, pCodec, nullptr))) 103 | return { std::move(File), std::move(Decoder) }; 104 | } 105 | return {}; 106 | } 107 | 108 | using std::chrono::microseconds; 109 | 110 | static constexpr bool isSameTimeUnit = 111 | std::is_same_v>; 112 | static_assert(isSameTimeUnit, "libav uses different time units"); 113 | 114 | auto getTickDuration(const libav::File & File) { 115 | return microseconds{ av_rescale_q(1, File->streams[FirstStream]->time_base, 116 | { 1, AV_TIME_BASE }) }; 117 | } 118 | 119 | constexpr auto makeVideoFrame(const libav::Frame & Frame, int FrameNumber, 120 | microseconds TickDuration) { 121 | FrameHeader Header = { .Width_ = Frame->width, 122 | .Height_ = Frame->height, 123 | .LinePitch_ = Frame->linesize[MainSubstream], 124 | .Format_ = std::to_underlying(fromLibav(Frame->format)), 125 | .Sequence_ = FrameNumber, 126 | .Timestamp_ = TickDuration * Frame->pts }; 127 | 128 | tPixels Pixels = { std::bit_cast(Frame->data[MainSubstream]), 129 | Header.SizePixels() }; 130 | return video::Frame{ Header, Pixels }; 131 | } 132 | 133 | #define DECODER_HAS(x) \ 134 | (requires(T Decoder) { \ 135 | { Decoder->x }; \ 136 | }) 137 | 138 | template 139 | auto FrameNumber(const T & Decoder) { 140 | if constexpr (DECODER_HAS(frame_num)) 141 | return static_cast(Decoder->frame_num); 142 | else if constexpr (DECODER_HAS(frame_number)) 143 | return Decoder->frame_number; 144 | } 145 | 146 | auto decodeFrames(libav::File File, libav::Codec Decoder) 147 | -> std::generator { 148 | const auto TickDuration = getTickDuration(File); 149 | libav::Packet Packet; 150 | libav::Frame Frame; 151 | 152 | int Result = 0; 153 | while (not atEndOfFile(Result) and successful(av_read_frame(File, Packet))) { 154 | const auto PacketReferenceGuard = Packet.dropReference(); 155 | if (Packet->stream_index != FirstStream) 156 | continue; 157 | Result = avcodec_send_packet(Decoder, Packet); 158 | while (successful(Result)) { 159 | Result = avcodec_receive_frame(Decoder, Frame); 160 | if (successful(Result)) 161 | co_yield makeVideoFrame(Frame, FrameNumber(Decoder), TickDuration); 162 | } 163 | } 164 | } 165 | 166 | // "borrowing" is safe due to 'consteval' 😊 167 | consteval auto hasExtension(std::string_view Extension) { 168 | return [=](const fs::path & p) { 169 | return p.empty() or p.extension() == Extension; 170 | }; 171 | } 172 | 173 | using namespace std::chrono_literals; 174 | 175 | // clang-format off 176 | auto makeFrames(fs::path Directory) -> std::generator { 177 | auto PreprocessedMediaFiles = InfinitePathSource(std::move(Directory)) 178 | | vws::filter(hasExtension(".gif")) 179 | | vws::transform(tryOpenAsGIF) 180 | | vws::transform(tryOpenVideoDecoder) 181 | ; 182 | for (auto [File, Decoder] : PreprocessedMediaFiles) { 183 | if (have(Decoder)) { 184 | std::println("decoding <{}>", File->url); 185 | co_yield rgs::elements_of( 186 | decodeFrames(std::move(File), std::move(Decoder))); 187 | } else { 188 | co_yield video::makeFillerFrame(100ms); 189 | } 190 | } 191 | } 192 | } // namespace video 193 | -------------------------------------------------------------------------------- /Demo-App/videodecoder.ixx: -------------------------------------------------------------------------------- 1 | export module video:decoder; 2 | import std; 3 | 4 | import :frame; 5 | 6 | namespace video { 7 | export std::generator makeFrames(std::filesystem::path); 8 | } 9 | -------------------------------------------------------------------------------- /Demo-App/videoframe.ixx: -------------------------------------------------------------------------------- 1 | export module video:frame; 2 | import std; 3 | 4 | import libav; 5 | 6 | export namespace video { 7 | enum class PixelFormat : unsigned char { invalid, RGBA, BGRA, _largest = BGRA }; 8 | 9 | consteval auto FormatBits() { 10 | return std::bit_width(std::to_underlying(PixelFormat::_largest)); 11 | } 12 | 13 | constexpr PixelFormat fromLibav(int Format) { 14 | using enum PixelFormat; 15 | switch (Format) { 16 | case AVPixelFormat::AV_PIX_FMT_RGBA: return RGBA; 17 | case AVPixelFormat::AV_PIX_FMT_BGRA: return BGRA; 18 | default: return invalid; 19 | } 20 | } 21 | 22 | namespace chrono = std::chrono; 23 | 24 | struct FrameHeader { 25 | static constexpr auto SizeBytes = 12u; 26 | 27 | using µSeconds = chrono::duration; 28 | 29 | int Width_ : 16; 30 | int Height_ : 16; 31 | int LinePitch_ : 16; 32 | int Format_ : FormatBits(); 33 | int Sequence_ : 16 - FormatBits(); 34 | µSeconds Timestamp_; 35 | 36 | [[nodiscard]] constexpr size_t SizePixels() const noexcept { 37 | return static_cast(Height_) * LinePitch_; 38 | } 39 | constexpr bool hasNoPixels() const noexcept { return SizePixels() == 0; } 40 | constexpr bool isFiller() const noexcept { 41 | return Sequence_ == 0 and Timestamp_.count() > 0; 42 | } 43 | constexpr bool isNoFrame() const noexcept { 44 | return Sequence_ == 0 and Timestamp_.count() <= 0; 45 | } 46 | constexpr bool isFirstFrame() const noexcept { return Sequence_ <= 1; } 47 | }; 48 | static_assert(sizeof(FrameHeader) == FrameHeader::SizeBytes); 49 | static_assert(std::is_trivial_v, 50 | "Please keep me trivial"); // guarantee relocatability! 51 | static_assert(std::is_trivially_destructible_v, 52 | "Please keep me 'implicit lifetime'"); 53 | 54 | using tPixels = std::span; 55 | 56 | struct Frame { 57 | FrameHeader Header_; 58 | tPixels Pixels_; 59 | 60 | [[nodiscard]] constexpr std::size_t TotalSize() const noexcept { 61 | return FrameHeader::SizeBytes + Pixels_.size_bytes(); 62 | } 63 | }; 64 | 65 | constexpr inline video::Frame noFrame{ 0 }; 66 | 67 | constexpr video::Frame makeFillerFrame(chrono::milliseconds Duration) { 68 | auto Filler = noFrame; 69 | Filler.Header_.Timestamp_ = chrono::duration_cast(Duration); 70 | return Filler; 71 | } 72 | } // namespace video 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CC BY-SA 4.0][cc-by-sa-shield]][cc-by-sa] 2 | 3 | # CppInAction 4 | 5 | Demo Code for presentation "Contemporary C++ In Action" 6 | 7 | This code does not compile without the missing pieces (the precompiled modules mentioned in the code) because it is for educational purposes only. Besides that it is complete. 8 | 9 | The missing pieces are: 10 | 1) Asio, preferably non-Boost [Asio](https://think-async.com/Asio) 11 | 2) libav [FFmpeg](https://ffmpeg.org/download.html) 12 | 3) SDL2 [SDL](https://www.libsdl.org/download-2.0.php) 13 | 4) argparse [argparse](https://github.com/p-ranav/argparse) 14 | 5) modularized standard library 15 | 16 | I have forked 17 | 1) [Asio, branch 'module'](https://github.com/DanielaE/asio/tree/module) 18 | 2) [SDL2, branch 'module'](https://github.com/DanielaE/SDL/tree/module) 19 | 3) [argparse, branch 'module'](https://github.com/DanielaE/argparse/tree/module) 20 | 4) [libav, branch 'module'](https://github.com/DanielaE/libav.module/tree/module) 21 | 22 | plus 23 | 24 | 5) a modularized standard library with a polyfill for the missing, not yet implemented parts: [std.module](https://github.com/DanielaE/std.module/tree/module) 25 | 26 | which contain the necessary changes to compile Asio, SDL, libav, and argparse as modules. My take on the C++ standard library module adds Casey Carter's current implementation of ``, plus my implementation of `` and a partial implementation of C++26's *explicit lifetime management* [P2590](https://wg21.link/P2590) on top of the standard library that comes with the compiler toolset of your choice. 27 | 28 | There is also a [minimum set of sources](https://github.com/DanielaE/libav.module/tree/main) from FFmpeg v6.0 to compile module `libav`. You need to provide the necessary link libraries yourself if you want to build the executable. 29 | 30 | The videos of the keynote presentations are here: 31 | - [CppCon 2022](https://youtu.be/yUIFdL3D0Vk) 32 | - [Meeting C++ 2022](https://youtu.be/el-xE645Clo) 33 | 34 | ## Building the app 35 | ## Windows 36 | Update 4 or better is highly recommended. 37 | - open a VS2022 command line window 38 | - cmake -B bld-msvc -G Ninja -Wno-dev -DCMAKE_CXX_STANDARD=23 --fresh 39 | - ninja -C bld-msvc 40 | 41 | ## MSYS2 (UCRT64) 42 | Clang 16.0.2 or better is required. 43 | - open a MSYS2 window 44 | - export CC=clang 45 | - export CXX=clang++ 46 | - cmake -B bld-clang -G Ninja -Wno-dev -DCMAKE_CXX_STANDARD=23 -DCMAKE_CXX_FLAGS="-stdlib=libc++" --fresh 47 | - ninja -C bld-clang 48 | 49 | ## Linux 50 | Clang 16.0.2 or better is required. Clang 17 is recommended. 51 | - open a terminal window 52 | - export CC=clang-1x 53 | - export CXX=clang++-1x 54 | - cmake -B bld -G Ninja -Wno-dev -DCMAKE_CXX_STANDARD=23 -DCMAKE_CXX_FLAGS="-stdlib=libc++" --fresh 55 | - ninja -C bld 56 | 57 | 58 | ### License 59 | This work is licensed under a 60 | [Creative Commons Attribution-ShareAlike 4.0 International License][cc-by-sa]. 61 | 62 | [![CC BY-SA 4.0][cc-by-sa-image]][cc-by-sa] 63 | 64 | [cc-by-sa]: http://creativecommons.org/licenses/by-sa/4.0/ 65 | [cc-by-sa-image]: https://licensebuttons.net/l/by-sa/4.0/88x31.png 66 | [cc-by-sa-shield]: https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg 67 | -------------------------------------------------------------------------------- /Slides/contemporary-c++-in-action-meeting++2022.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielaE/CppInAction/7961360303c51b254f7c9718381968e234f835a8/Slides/contemporary-c++-in-action-meeting++2022.pdf -------------------------------------------------------------------------------- /Slides/contemporary-c-in-action-adc2024.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielaE/CppInAction/7961360303c51b254f7c9718381968e234f835a8/Slides/contemporary-c-in-action-adc2024.pdf -------------------------------------------------------------------------------- /Slides/contemporary-c-in-action-cppcon2022.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielaE/CppInAction/7961360303c51b254f7c9718381968e234f835a8/Slides/contemporary-c-in-action-cppcon2022.pdf -------------------------------------------------------------------------------- /Slides/so-you-want-to-use-c++-modules-cross-platform-ndc-techtown2023.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielaE/CppInAction/7961360303c51b254f7c9718381968e234f835a8/Slides/so-you-want-to-use-c++-modules-cross-platform-ndc-techtown2023.pdf -------------------------------------------------------------------------------- /Slides/so-you-want-to-use-c-modules-cross-platform-meeting-cpp-2023.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielaE/CppInAction/7961360303c51b254f7c9718381968e234f835a8/Slides/so-you-want-to-use-c-modules-cross-platform-meeting-cpp-2023.pdf -------------------------------------------------------------------------------- /Slides/so-you-want-to-use-modules-cross-plattform-cpponsea2023.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielaE/CppInAction/7961360303c51b254f7c9718381968e234f835a8/Slides/so-you-want-to-use-modules-cross-plattform-cpponsea2023.pdf --------------------------------------------------------------------------------