├── .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
--------------------------------------------------------------------------------