├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── clang-format.bash ├── img └── priority_scheduling.png ├── include └── psched │ ├── aging_policy.h │ ├── priority_scheduler.h │ ├── queue_size.h │ ├── task.h │ ├── task_queue.h │ └── task_stats.h ├── psched.pc.in ├── pschedConfig.cmake.in ├── samples ├── CMakeLists.txt └── multiple_periodic_tasks.cpp ├── single_include.json ├── single_include └── psched │ └── psched.h └── utils └── amalgamate ├── CHANGES.md ├── LICENSE.md ├── README.md ├── amalgamate.py └── config.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 80 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: true 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: None 82 | ObjCBlockIndentWidth: 2 83 | ObjCSpaceAfterProperty: false 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Right 93 | RawStringFormats: 94 | - Language: TextProto 95 | Delimiters: 96 | - 'pb' 97 | - 'proto' 98 | BasedOnStyle: google 99 | ReflowComments: true 100 | SortIncludes: true 101 | SortUsingDeclarations: true 102 | SpaceAfterCStyleCast: false 103 | SpaceAfterTemplateKeyword: true 104 | SpaceBeforeAssignmentOperators: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceInEmptyParentheses: false 107 | SpacesBeforeTrailingComments: 1 108 | SpacesInAngles: false 109 | SpacesInContainerLiterals: true 110 | SpacesInCStyleCastParentheses: false 111 | SpacesInParentheses: false 112 | SpacesInSquareBrackets: false 113 | Standard: Cpp11 114 | TabWidth: 8 115 | UseTab: Never 116 | ... 117 | 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | .vscode/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | #*.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # CodeRush 261 | .cr/ 262 | 263 | # Python Tools for Visual Studio (PTVS) 264 | __pycache__/ 265 | *.pyc 266 | 267 | # CMake build directory 268 | build 269 | 270 | # Cppcheck build directory 271 | analysis-cppcheck-build-dir 272 | 273 | # Ideas directory 274 | ideas 275 | 276 | desktop.iniimages/ 277 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | if(DEFINED PROJECT_NAME) 4 | set(PSCHED_SUBPROJECT ON) 5 | endif() 6 | 7 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.12") 8 | project(psched VERSION 1.9.0 LANGUAGES CXX 9 | HOMEPAGE_URL "https://github.com/p-ranav/psched" 10 | DESCRIPTION "Priority-based Task Scheduling for Modern C++") 11 | elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.9") 12 | project(psched VERSION 1.9.0 LANGUAGES CXX 13 | DESCRIPTION "Priority-based Task Scheduling for Modern C++") 14 | else() 15 | project(psched VERSION 1.9.0 LANGUAGES CXX) 16 | endif() 17 | 18 | if(EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 19 | include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 20 | conan_basic_setup() 21 | endif() 22 | 23 | option(PSCHED_SAMPLES "Build psched samples") 24 | 25 | include(CMakePackageConfigHelpers) 26 | include(GNUInstallDirs) 27 | 28 | find_package(Threads REQUIRED) 29 | 30 | add_library(psched INTERFACE) 31 | add_library(psched::psched ALIAS psched) 32 | 33 | target_compile_features(psched INTERFACE cxx_std_17) 34 | 35 | if(NOT CMAKE_BUILD_TYPE) 36 | set(CMAKE_BUILD_TYPE Release) 37 | endif() 38 | 39 | set(CMAKE_CXX_FLAGS "-Wall -Wextra") 40 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 41 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") 42 | 43 | target_include_directories(psched INTERFACE 44 | $ 45 | $) 46 | target_link_libraries(psched INTERFACE Threads::Threads) 47 | 48 | if(PSCHED_SAMPLES) 49 | add_subdirectory(samples) 50 | endif() 51 | 52 | if(NOT PSCHED_SUBPROJECT) 53 | configure_package_config_file(pschedConfig.cmake.in 54 | ${CMAKE_CURRENT_BINARY_DIR}/pschedConfig.cmake 55 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/psched) 56 | write_basic_package_version_file(pschedConfigVersion.cmake 57 | COMPATIBILITY AnyNewerVersion) 58 | 59 | configure_file(psched.pc.in psched.pc @ONLY) 60 | 61 | install(TARGETS psched EXPORT pschedTargets) 62 | install(EXPORT pschedTargets 63 | FILE pschedTargets.cmake 64 | NAMESPACE psched:: 65 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/psched) 66 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pschedConfig.cmake 67 | ${CMAKE_CURRENT_BINARY_DIR}/pschedConfigVersion.cmake 68 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/psched) 69 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/psched.pc 70 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 71 | install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/psched 72 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 73 | USE_SOURCE_PERMISSIONS 74 | PATTERN "*.h") 75 | 76 | if(EXISTS "${PROJECT_SOURCE_DIR}/.gitignore") 77 | # Simple glob to regex conversion (.gitignore => CPACK_SOURCE_IGNORE_FILES) 78 | file(READ ".gitignore" DOT_GITIGNORE) 79 | string(REPLACE ";" "RANDOMSEQUENCE" DOT_GITIGNORE "${DOT_GITIGNORE}") 80 | string(REPLACE "\n" ";" DOT_GITIGNORE "${DOT_GITIGNORE}") 81 | string(REPLACE "RANDOMSEQUENCE" "\\;" DOT_GITIGNORE "${DOT_GITIGNORE}") 82 | foreach(IGNORE_LINE ${DOT_GITIGNORE}) 83 | if(NOT IGNORE_LINE OR IGNORE_LINE MATCHES "^#") 84 | continue() 85 | endif() 86 | string(REPLACE "\\" "\\\\" IGNORE_LINE "${IGNORE_LINE}") 87 | string(REPLACE "." "\\\\." IGNORE_LINE "${IGNORE_LINE}") 88 | string(REPLACE "*" ".*" IGNORE_LINE "${IGNORE_LINE}") 89 | string(REPLACE "+" "\\\\+" IGNORE_LINE "${IGNORE_LINE}") 90 | list(APPEND CPACK_SOURCE_IGNORE_FILES "${IGNORE_LINE}") 91 | endforeach() 92 | endif() 93 | 94 | # extra ignored files 95 | list(APPEND CPACK_SOURCE_IGNORE_FILES 96 | .editorconfig 97 | .git 98 | .gitignore 99 | .travis.yml 100 | .appveyor.yml 101 | ) 102 | set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") 103 | set(CPACK_GENERATOR "TGZ;TXZ") 104 | set(CPACK_SOURCE_GENERATOR "TGZ;TXZ") 105 | include(CPack) 106 | endif() 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pranav Srinivas Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # psched 2 | 3 | `psched` is a lightweight library that provides a priority-based task scheduler for modern C++. 4 | 5 | * The `psched` scheduler manages an array of concurrent queues, each queue assigned a priority-level 6 | * A task, when scheduled, is enqueued onto one of queues based on the task's priority 7 | * A pool of threads executes ready tasks, starting with the highest priority 8 | * The priority of starving tasks is modulated based on the age of the task 9 | 10 |

11 | 12 |

13 | 14 | ## Getting Started 15 | 16 | Consider the task set below. There are three periodic tasks: `a`, `b` and `c`. 17 | 18 | | Task | Period (ms) | Burst Time (ms) | Priority | 19 | |------|-------------|-----------------|-------------| 20 | | a | 250 | 130 | 0 (Lowest) | 21 | | b | 500 | 390 | 1 | 22 | | c | 1000 | 560 | 2 (Highest) | 23 | 24 | Here, _burst time_ refers to the amount of time required by the task for executing on CPU. 25 | 26 | ### Create a `PriorityScheduler` 27 | 28 | First, let's create a scheduler: 29 | 30 | ```cpp 31 | #include 32 | #include 33 | using namespace psched; 34 | 35 | int main() { 36 | PriorityScheduler, 37 | queues<3, maintain_size<100, discard::oldest_task>>, 38 | aging_policy< 39 | task_starvation_after, 40 | increment_priority_by<1> 41 | >> 42 | scheduler; 43 | ``` 44 | 45 | ### Create a `Task` 46 | 47 | Create the first task, `Task a` like below. The task "performs work" for 130ms. The post-completion callback, called when the task has completed, can be used to study the temporal behavior of the task, e.g., waiting time, burst time, and turnaround time. 48 | 49 | ```cpp 50 | 51 | Task a( 52 | // Task action 53 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(130)); }, 54 | 55 | // Task post-completion callback 56 | [](const TaskStats &stats) { 57 | std::cout << "[Task a] "; 58 | std::cout << "Waiting time = " << stats.waiting_time() << "ms; "; 59 | std::cout << "Burst time = " << stats.burst_time() << "ms; "; 60 | std::cout << "Turnaround time = " << stats.turnaround_time() << "ms\n"; 61 | } 62 | ); 63 | ``` 64 | 65 | ### Schedule the task 66 | 67 | Now, we can write a simple periodic timer that schedules this task at `priority<0>`: 68 | 69 | ```cpp 70 | auto timer_a = std::thread([&scheduler, &a]() { 71 | while (true) { 72 | 73 | // Schedule the task 74 | scheduler.schedule>(a); 75 | 76 | // Sleep for 250ms and repeat 77 | std::this_thread::sleep_for(std::chrono::milliseconds(250)); 78 | } 79 | }); 80 | ``` 81 | 82 | ### Schedule more tasks 83 | 84 | We can repeat the above code for tasks `b` and `c`: 85 | 86 | ```cpp 87 | Task b( 88 | // Task action 89 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(390)); }, 90 | // Task post-completion callback 91 | [](const TaskStats &stats) { 92 | std::cout << "[Task b] "; 93 | std::cout << "Waiting time = " << stats.waiting_time() << "ms; "; 94 | std::cout << "Burst time = " << stats.burst_time() << "ms; "; 95 | std::cout << "Turnaround time = " << stats.turnaround_time() << "ms\n"; 96 | }); 97 | 98 | auto timer_b = std::thread([&scheduler, &b]() { 99 | while (true) { 100 | scheduler.schedule>(b); 101 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 102 | } 103 | }); 104 | 105 | Task c( 106 | // Task action 107 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(560)); }, 108 | // Task post-completion callback 109 | [](const TaskStats &stats) { 110 | std::cout << "[Task c] "; 111 | std::cout << "Waiting time = " << stats.waiting_time() << "ms; "; 112 | std::cout << "Burst time = " << stats.burst_time() << "ms; "; 113 | std::cout << "Turnaround time = " << stats.turnaround_time() << "ms\n"; 114 | }); 115 | 116 | auto timer_c = std::thread([&scheduler, &c]() { 117 | while (true) { 118 | scheduler.schedule>(c); 119 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 120 | } 121 | }); 122 | 123 | timer_a.join(); 124 | timer_b.join(); 125 | timer_c.join(); 126 | } 127 | ``` 128 | 129 | Running this sample may yield the following output: 130 | 131 | ```bash 132 | ./multiple_periodic_tasks 133 | [Task a] Waiting time = 0ms; Burst time = 133ms; Turnaround time = 133ms 134 | [Task c] Waiting time = 0ms; Burst time = 563ms; Turnaround time = 563ms 135 | [Task b] Waiting time = 0ms; Burst time = 395ms; Turnaround time = 395ms 136 | [Task a] Waiting time = 60ms; Burst time = 134ms; Turnaround time = 194ms 137 | [Task a] Waiting time = 0ms; Burst time = 131ms; Turnaround time = 131ms 138 | [Task b] Waiting time = 0ms; Burst time = 390ms; Turnaround time = 390ms 139 | [Task a] Waiting time = 3ms; Burst time = 135ms; Turnaround time = 139ms 140 | [Task a] Waiting time = 0ms; Burst time = 132ms; Turnaround time = 132ms 141 | [Task b] Waiting time = 8ms; Burst time = 393ms; Turnaround time = 402ms 142 | [Task c] Waiting time = 0ms; Burst time = 561ms; Turnaround time = 561ms 143 | [Task a] Waiting time = 6ms; Burst time = 133ms; Turnaround time = 139ms 144 | [Task b] Waiting time = [Task a] Waiting time = 0ms; Burst time = 393ms; Turnaround time = 393ms 145 | 0ms; Burst time = 133ms; Turnaround time = 133ms 146 | [Task a] Waiting time = 11ms; Burst time = 134ms; Turnaround time = 145ms 147 | [Task a] Waiting time = 0ms; Burst time = 134ms; Turnaround time = 134ms 148 | [Task b] Waiting time = 11ms; Burst time = 394ms; Turnaround time = 405ms 149 | [Task c] Waiting time = 0ms; Burst time = 560ms; Turnaround time = 560ms 150 | [Task a] Waiting time = 7ms; Burst time = 132ms; Turnaround time = 139ms 151 | [Task b] Waiting time = 0ms; Burst time = 390ms; Turnaround time = 390ms 152 | [Task a] Waiting time = 0ms; Burst time = 130ms; Turnaround time = 130ms 153 | [Task a] Waiting time = 17ms; Burst time = 130ms; Turnaround time = 148ms 154 | [Task a] Waiting time = 0ms; Burst time = 131ms; Turnaround time = 131ms 155 | [Task b] Waiting time = 10ms; Burst time = 390ms; Turnaround time = 401ms 156 | [Task c] Waiting time = 0ms; Burst time = 560ms; Turnaround time = 560ms 157 | ``` 158 | 159 | ## Building Samples 160 | 161 | ```bash 162 | git clone https://github.com/p-ranav/psched 163 | cd psched 164 | mkdir build && cd build 165 | cmake -DPSCHED_SAMPLES=ON .. 166 | make 167 | ``` 168 | 169 | ## Generating Single Header 170 | 171 | ```bash 172 | python3 utils/amalgamate/amalgamate.py -c single_include.json -s . 173 | ``` 174 | 175 | ## Contributing 176 | Contributions are welcome, have a look at the CONTRIBUTING.md document for more information. 177 | 178 | ## License 179 | The project is available under the MIT license. 180 | -------------------------------------------------------------------------------- /clang-format.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find ./include ./samples/ ./single_include -type f \( -iname \*.cpp -o -iname \*.h \) | xargs clang-format -style="{ColumnLimit : 100}" -i 3 | -------------------------------------------------------------------------------- /img/priority_scheduling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-ranav/psched/d0a966af78a65e9e9df4083411d1e7dc5388881e/img/priority_scheduling.png -------------------------------------------------------------------------------- /include/psched/aging_policy.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | namespace psched { 6 | 7 | template struct is_chrono_duration { static constexpr bool value = false; }; 8 | 9 | template 10 | struct is_chrono_duration> { 11 | static constexpr bool value = true; 12 | }; 13 | 14 | template struct task_starvation_after { 15 | static_assert(is_chrono_duration::value, "Duration must be a std::chrono::duration"); 16 | typedef D type; 17 | constexpr static D value = D(P); 18 | }; 19 | 20 | template struct increment_priority_by { constexpr static size_t value = P; }; 21 | 22 | template , class I = increment_priority_by<1>> 23 | struct aging_policy { 24 | typedef T task_starvation_after; 25 | typedef I increment_priority_by; 26 | }; 27 | 28 | } // namespace psched -------------------------------------------------------------------------------- /include/psched/priority_scheduler.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace psched { 14 | 15 | template struct threads { constexpr static size_t value = T; }; 16 | 17 | template struct priority { constexpr static size_t value = P; }; 18 | 19 | template class PriorityScheduler { 20 | constexpr static size_t priority_levels = queues::number_of_queues; 21 | 22 | std::vector threads_{}; // Scheduler thread pool 23 | std::array, priority_levels> priority_queues_{}; // Array of task queues 24 | std::atomic_bool running_{false}; // Is the scheduler running? 25 | std::mutex mutex_{}; // Mutex to protect `enqueued_` 26 | std::condition_variable ready_{}; // Signal to notify task enqueued 27 | std::atomic_size_t enqueued_{0}; // Incremented when a task is scheduled 28 | 29 | void run() { 30 | while (running_ || enqueued_ > 0) { 31 | // Wait for the `enqueued` signal 32 | { 33 | std::unique_lock lock{mutex_}; 34 | ready_.wait(lock, [this] { return enqueued_ > 0 || !running_; }); 35 | } 36 | 37 | Task t; 38 | 39 | // Handle task starvation at lower priorities 40 | // Modulate priorities based on age 41 | // Start from the lowest priority till (highest_priority - 1) 42 | for (size_t i = 0; i < priority_levels - 1; i++) { 43 | // Check if the front of the queue has a starving task 44 | if (priority_queues_[i] 45 | .template try_pop_if_starved(t)) { 46 | // task has been starved, reschedule at a higher priority 47 | while (running_) { 48 | const auto new_priority = 49 | std::min(i + aging_policy::increment_priority_by::value, priority_levels - 1); 50 | if (priority_queues_[new_priority].try_push(t)) { 51 | break; 52 | } 53 | } 54 | } 55 | } 56 | 57 | // Run the highest priority ready task 58 | bool dequeued = false; 59 | 60 | while (!dequeued) { 61 | for (size_t i = priority_levels; i > 0; --i) { 62 | // Try to pop an item 63 | if (priority_queues_[i - 1].try_pop(t)) { 64 | dequeued = true; 65 | if (enqueued_ > 0) { 66 | enqueued_ -= 1; 67 | } 68 | // execute task 69 | t(); 70 | break; 71 | } 72 | } 73 | if (!(running_ || enqueued_ > 0)) 74 | break; 75 | } 76 | } 77 | } 78 | 79 | public: 80 | PriorityScheduler() { 81 | running_ = true; 82 | for (unsigned n = 0; n != threads::value; ++n) { 83 | threads_.emplace_back([this] { run(); }); 84 | } 85 | } 86 | 87 | ~PriorityScheduler() { 88 | running_ = false; 89 | ready_.notify_all(); 90 | for (auto &q : priority_queues_) 91 | q.done(); 92 | for (auto &t : threads_) 93 | if (t.joinable()) 94 | t.join(); 95 | } 96 | 97 | template void schedule(Task &task) { 98 | static_assert(priority::value <= priority_levels, "priority out of range"); 99 | 100 | // Enqueue task 101 | while (running_) { 102 | if (priority_queues_[priority::value].try_push(task)) { 103 | break; 104 | } 105 | } 106 | 107 | // Send `enqueued` signal to worker threads 108 | { 109 | std::unique_lock lock{mutex_}; 110 | enqueued_ += 1; 111 | ready_.notify_one(); 112 | } 113 | } 114 | 115 | void stop() { 116 | running_ = false; 117 | ready_.notify_all(); 118 | for (auto &q : priority_queues_) 119 | q.done(); 120 | for (auto &t : threads_) 121 | if (t.joinable()) 122 | t.join(); 123 | } 124 | }; 125 | 126 | } // namespace psched 127 | -------------------------------------------------------------------------------- /include/psched/queue_size.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | namespace psched { 6 | 7 | enum class discard { oldest_task, newest_task }; 8 | 9 | template struct maintain_size { 10 | constexpr static size_t bounded_queue_size = queue_size; 11 | constexpr static discard discard_policy = policy; 12 | }; 13 | 14 | template > struct queues { 15 | constexpr static bool bounded_or_not = (M::bounded_queue_size > 0); 16 | constexpr static size_t number_of_queues = count; 17 | typedef M maintain_size; 18 | }; 19 | 20 | } // namespace psched -------------------------------------------------------------------------------- /include/psched/task.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | 7 | namespace psched { 8 | 9 | class Task { 10 | // Called when the task is (finally) executed by an executor thread 11 | std::function task_main_; 12 | 13 | // Called after the task has completed executing. 14 | // In case of exception, `task_error` is called first 15 | // 16 | // TaskStats argument can be used to get task computation_time 17 | // and task response_time. 18 | std::function task_end_; 19 | 20 | // Called if `task_main()` throws an exception 21 | std::function task_error_; 22 | 23 | // Temporal behavior of Task 24 | // Stats includes arrival_time, start_time, end_time 25 | // Stats can be used to calculate waiting_time, burst_time, turnaround_time 26 | TaskStats stats_; 27 | 28 | template friend class TaskQueue; 29 | 30 | protected: 31 | void save_arrival_time() { stats_.arrival_time = std::chrono::steady_clock::now(); } 32 | 33 | public: 34 | Task(const std::function &task_main = {}, 35 | const std::function &task_end = {}, 36 | const std::function &task_error = {}) 37 | : task_main_(task_main), task_end_(task_end), task_error_(task_error) {} 38 | 39 | Task(const Task &other) { 40 | task_main_ = other.task_main_; 41 | task_end_ = other.task_end_; 42 | task_error_ = other.task_error_; 43 | stats_ = other.stats_; 44 | } 45 | 46 | Task &operator=(Task other) { 47 | std::swap(task_main_, other.task_main_); 48 | std::swap(task_end_, other.task_end_); 49 | std::swap(task_error_, other.task_error_); 50 | std::swap(stats_, other.stats_); 51 | return *this; 52 | } 53 | 54 | void on_execute(const std::function &fn) { task_main_ = fn; } 55 | 56 | void on_complete(const std::function &fn) { task_end_ = fn; } 57 | 58 | void on_error(const std::function &fn) { task_error_ = fn; } 59 | 60 | void operator()() { 61 | stats_.start_time = std::chrono::steady_clock::now(); 62 | try { 63 | if (task_main_) { 64 | task_main_(); 65 | } 66 | stats_.end_time = std::chrono::steady_clock::now(); 67 | } catch (std::exception &e) { 68 | stats_.end_time = std::chrono::steady_clock::now(); 69 | if (task_error_) { 70 | task_error_(e.what()); 71 | } 72 | } catch (...) { 73 | stats_.end_time = std::chrono::steady_clock::now(); 74 | if (task_error_) { 75 | task_error_("Unknown exception"); 76 | } 77 | } 78 | if (task_end_) { 79 | task_end_(stats_); 80 | } 81 | } 82 | }; 83 | 84 | } // namespace psched -------------------------------------------------------------------------------- /include/psched/task_queue.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace psched { 11 | 12 | template class TaskQueue { 13 | std::deque queue_; // Internal queue data structure 14 | bool done_{false}; // Set to true when no more tasks are expected 15 | std::mutex mutex_; // Mutex for the internal queue 16 | std::condition_variable ready_; // Signal for when a task is enqueued 17 | 18 | public: 19 | bool try_pop(Task &task) { 20 | std::unique_lock lock{mutex_, std::try_to_lock}; 21 | if (!lock || queue_.empty()) 22 | return false; 23 | task = std::move(queue_.front()); 24 | queue_.pop_front(); 25 | return true; 26 | } 27 | 28 | bool try_push(Task &task) { 29 | { 30 | std::unique_lock lock{mutex_, std::try_to_lock}; 31 | if (!lock) 32 | return false; 33 | task.save_arrival_time(); 34 | queue_.emplace_back(task); 35 | 36 | // Is the queue bounded? 37 | if (queue_policy::bounded_or_not) { 38 | while (queue_.size() > queue_policy::maintain_size::bounded_queue_size) { 39 | // Queue size greater than bound 40 | if (queue_policy::maintain_size::discard_policy == discard::newest_task) { 41 | queue_.pop_back(); // newest task is in the back of the queue 42 | } else if (queue_policy::maintain_size::discard_policy == discard::oldest_task) { 43 | queue_.pop_front(); // oldest task is in the front of the queue 44 | } 45 | } 46 | } 47 | } 48 | ready_.notify_one(); 49 | return true; 50 | } 51 | 52 | void done() { 53 | { 54 | std::unique_lock lock{mutex_}; 55 | done_ = true; 56 | } 57 | ready_.notify_all(); 58 | } 59 | 60 | template bool try_pop_if_starved(Task &task) { 61 | std::unique_lock lock{mutex_, std::try_to_lock}; 62 | if (!lock || queue_.empty()) 63 | return false; 64 | task = queue_.front(); 65 | const auto now = std::chrono::steady_clock::now(); 66 | const auto diff = std::chrono::duration_cast(now - task.stats_.arrival_time); 67 | if (diff > A::value) { 68 | // pop the task so it can be enqueued at a higher priority 69 | task = std::move(queue_.front()); 70 | queue_.pop_front(); 71 | return true; 72 | } 73 | return false; 74 | } 75 | }; 76 | 77 | } // namespace psched -------------------------------------------------------------------------------- /include/psched/task_stats.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | namespace psched { 6 | 7 | struct TaskStats { 8 | using TimePoint = std::chrono::steady_clock::time_point; 9 | TimePoint arrival_time; // time point when the task is marked as 'ready' (queued) 10 | TimePoint start_time; // time point when the task is about to execute (dequeued) 11 | TimePoint end_time; // time point when the task completes execution 12 | 13 | // Waiting time is the amount of time spent by a task waiting 14 | // in the ready queue for getting the CPU. 15 | template long long waiting_time() const { 16 | return std::chrono::duration_cast(start_time - arrival_time).count(); 17 | } 18 | 19 | // Burst time is the amount of time required by a task for executing on CPU. 20 | // It is also called as execution time or running time. 21 | template long long burst_time() const { 22 | return std::chrono::duration_cast(end_time - start_time).count(); 23 | } 24 | 25 | // Turnaround time (TAT) is the time interval from the time of submission 26 | // of a task to the time of the completion of the task. It can also be 27 | // considered as the sum of the time periods spent waiting to get into memory or 28 | // ready queue, execution on CPU and executing input/output. 29 | // 30 | // waiting_time() + burst_time() 31 | template long long turnaround_time() const { 32 | return std::chrono::duration_cast(end_time - arrival_time).count(); 33 | } 34 | }; 35 | 36 | } // namespace psched -------------------------------------------------------------------------------- /psched.pc.in: -------------------------------------------------------------------------------- 1 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 2 | 3 | Name: @PROJECT_NAME@ 4 | Description: @PROJECT_DESCRIPTION@ 5 | URL: @PROJECT_HOMEPAGE_URL@ 6 | Version: @PROJECT_VERSION@ 7 | Cflags: -I${includedir} 8 | -------------------------------------------------------------------------------- /pschedConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | find_dependency(Threads REQUIRED) 6 | 7 | if (NOT TARGET psched::psched) 8 | include(${CMAKE_CURRENT_LIST_DIR}/pschedTargets.cmake) 9 | endif () 10 | -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(multiple_periodic_tasks multiple_periodic_tasks.cpp) 2 | target_link_libraries(multiple_periodic_tasks PRIVATE psched::psched) -------------------------------------------------------------------------------- /samples/multiple_periodic_tasks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace psched; 4 | 5 | /* 6 | | Task | Period (ms) | Burst Time (ms) | Priority | 7 | |------|-------------|-----------------|-------------| 8 | | a | 250 | 130 | 0 (Lowest) | 9 | | b | 500 | 390 | 1 | 10 | | c | 1000 | 560 | 2 (Highest) | 11 | */ 12 | 13 | int main() { 14 | PriorityScheduler< 15 | threads<3>, queues<3, maintain_size<100, discard::oldest_task>>, 16 | aging_policy, increment_priority_by<1>>> 17 | scheduler; 18 | 19 | Task a( 20 | // Task action 21 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(130)); }, 22 | // Task post-completion callback 23 | [](const TaskStats &stats) { 24 | std::cout << "[Task a] "; 25 | std::cout << "Waiting time = " << stats.waiting_time() << "ms; "; 26 | std::cout << "Burst time = " << stats.burst_time() << "ms; "; 27 | std::cout << "Turnaround time = " << stats.turnaround_time() << "ms\n"; 28 | }); 29 | 30 | auto timer_a = std::thread([&scheduler, &a]() { 31 | while (true) { 32 | scheduler.schedule>(a); 33 | std::this_thread::sleep_for(std::chrono::milliseconds(250)); 34 | } 35 | }); 36 | 37 | Task b( 38 | // Task action 39 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(390)); }, 40 | // Task post-completion callback 41 | [](const TaskStats &stats) { 42 | std::cout << "[Task b] "; 43 | std::cout << "Waiting time = " << stats.waiting_time() << "ms; "; 44 | std::cout << "Burst time = " << stats.burst_time() << "ms; "; 45 | std::cout << "Turnaround time = " << stats.turnaround_time() << "ms\n"; 46 | }); 47 | 48 | auto timer_b = std::thread([&scheduler, &b]() { 49 | while (true) { 50 | scheduler.schedule>(b); 51 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 52 | } 53 | }); 54 | 55 | Task c( 56 | // Task action 57 | [] { std::this_thread::sleep_for(std::chrono::milliseconds(500)); }, 58 | // Task post-completion callback 59 | [](const TaskStats &stats) { 60 | std::cout << "[Task c] "; 61 | std::cout << "Waiting time = " << stats.waiting_time() << "ms; "; 62 | std::cout << "Burst time = " << stats.burst_time() << "ms; "; 63 | std::cout << "Turnaround time = " << stats.turnaround_time() << "ms\n"; 64 | }); 65 | 66 | auto timer_c = std::thread([&scheduler, &c]() { 67 | while (true) { 68 | scheduler.schedule>(c); 69 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 70 | } 71 | }); 72 | 73 | timer_a.join(); 74 | timer_b.join(); 75 | timer_c.join(); 76 | } -------------------------------------------------------------------------------- /single_include.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "Priority-based Task Scheduling for Modern C++", 3 | "target": "single_include/psched/psched.h", 4 | "sources": [ 5 | "include/psched/task_stats.h", 6 | "include/psched/queue_size.h", 7 | "include/psched/task.h", 8 | "include/psched/task_queue.h", 9 | "include/psched/aging_policy.h", 10 | "include/psched/priority_scheduler.h" 11 | ], 12 | "include_paths": ["include"] 13 | } 14 | -------------------------------------------------------------------------------- /single_include/psched/psched.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | namespace psched { 6 | 7 | struct TaskStats { 8 | using TimePoint = std::chrono::steady_clock::time_point; 9 | TimePoint arrival_time; // time point when the task is marked as 'ready' (queued) 10 | TimePoint start_time; // time point when the task is about to execute (dequeued) 11 | TimePoint end_time; // time point when the task completes execution 12 | 13 | // Waiting time is the amount of time spent by a task waiting 14 | // in the ready queue for getting the CPU. 15 | template long long waiting_time() const { 16 | return std::chrono::duration_cast(start_time - arrival_time).count(); 17 | } 18 | 19 | // Burst time is the amount of time required by a task for executing on CPU. 20 | // It is also called as execution time or running time. 21 | template long long burst_time() const { 22 | return std::chrono::duration_cast(end_time - start_time).count(); 23 | } 24 | 25 | // Turnaround time (TAT) is the time interval from the time of submission 26 | // of a task to the time of the completion of the task. It can also be 27 | // considered as the sum of the time periods spent waiting to get into memory or 28 | // ready queue, execution on CPU and executing input/output. 29 | // 30 | // waiting_time() + burst_time() 31 | template long long turnaround_time() const { 32 | return std::chrono::duration_cast(end_time - arrival_time).count(); 33 | } 34 | }; 35 | 36 | } // namespace psched 37 | #pragma once 38 | #include 39 | 40 | namespace psched { 41 | 42 | enum class discard { oldest_task, newest_task }; 43 | 44 | template struct maintain_size { 45 | constexpr static size_t bounded_queue_size = queue_size; 46 | constexpr static discard discard_policy = policy; 47 | }; 48 | 49 | template > struct queues { 50 | constexpr static bool bounded_or_not = (M::bounded_queue_size > 0); 51 | constexpr static size_t number_of_queues = count; 52 | typedef M maintain_size; 53 | }; 54 | 55 | } // namespace psched 56 | #pragma once 57 | #include 58 | #include 59 | // #include 60 | 61 | namespace psched { 62 | 63 | class Task { 64 | // Called when the task is (finally) executed by an executor thread 65 | std::function task_main_; 66 | 67 | // Called after the task has completed executing. 68 | // In case of exception, `task_error` is called first 69 | // 70 | // TaskStats argument can be used to get task computation_time 71 | // and task response_time. 72 | std::function task_end_; 73 | 74 | // Called if `task_main()` throws an exception 75 | std::function task_error_; 76 | 77 | // Temporal behavior of Task 78 | // Stats includes arrival_time, start_time, end_time 79 | // Stats can be used to calculate waiting_time, burst_time, turnaround_time 80 | TaskStats stats_; 81 | 82 | template friend class TaskQueue; 83 | 84 | protected: 85 | void save_arrival_time() { stats_.arrival_time = std::chrono::steady_clock::now(); } 86 | 87 | public: 88 | Task(const std::function &task_main = {}, 89 | const std::function &task_end = {}, 90 | const std::function &task_error = {}) 91 | : task_main_(task_main), task_end_(task_end), task_error_(task_error) {} 92 | 93 | Task(const Task &other) { 94 | task_main_ = other.task_main_; 95 | task_end_ = other.task_end_; 96 | task_error_ = other.task_error_; 97 | stats_ = other.stats_; 98 | } 99 | 100 | Task &operator=(Task other) { 101 | std::swap(task_main_, other.task_main_); 102 | std::swap(task_end_, other.task_end_); 103 | std::swap(task_error_, other.task_error_); 104 | std::swap(stats_, other.stats_); 105 | return *this; 106 | } 107 | 108 | void on_execute(const std::function &fn) { task_main_ = fn; } 109 | 110 | void on_complete(const std::function &fn) { task_end_ = fn; } 111 | 112 | void on_error(const std::function &fn) { task_error_ = fn; } 113 | 114 | void operator()() { 115 | stats_.start_time = std::chrono::steady_clock::now(); 116 | try { 117 | if (task_main_) { 118 | task_main_(); 119 | } 120 | stats_.end_time = std::chrono::steady_clock::now(); 121 | } catch (std::exception &e) { 122 | stats_.end_time = std::chrono::steady_clock::now(); 123 | if (task_error_) { 124 | task_error_(e.what()); 125 | } 126 | } catch (...) { 127 | stats_.end_time = std::chrono::steady_clock::now(); 128 | if (task_error_) { 129 | task_error_("Unknown exception"); 130 | } 131 | } 132 | if (task_end_) { 133 | task_end_(stats_); 134 | } 135 | } 136 | }; 137 | 138 | } // namespace psched 139 | #pragma once 140 | #include 141 | #include 142 | #include 143 | #include 144 | // #include 145 | // #include 146 | 147 | namespace psched { 148 | 149 | template class TaskQueue { 150 | std::deque queue_; // Internal queue data structure 151 | bool done_{false}; // Set to true when no more tasks are expected 152 | std::mutex mutex_; // Mutex for the internal queue 153 | std::condition_variable ready_; // Signal for when a task is enqueued 154 | 155 | public: 156 | bool try_pop(Task &task) { 157 | std::unique_lock lock{mutex_, std::try_to_lock}; 158 | if (!lock || queue_.empty()) 159 | return false; 160 | task = std::move(queue_.front()); 161 | queue_.pop_front(); 162 | return true; 163 | } 164 | 165 | bool try_push(Task &task) { 166 | { 167 | std::unique_lock lock{mutex_, std::try_to_lock}; 168 | if (!lock) 169 | return false; 170 | task.save_arrival_time(); 171 | queue_.emplace_back(task); 172 | 173 | // Is the queue bounded? 174 | if (queue_policy::bounded_or_not) { 175 | while (queue_.size() > queue_policy::maintain_size::bounded_queue_size) { 176 | // Queue size greater than bound 177 | if (queue_policy::maintain_size::discard_policy == discard::newest_task) { 178 | queue_.pop_back(); // newest task is in the back of the queue 179 | } else if (queue_policy::maintain_size::discard_policy == discard::oldest_task) { 180 | queue_.pop_front(); // oldest task is in the front of the queue 181 | } 182 | } 183 | } 184 | } 185 | ready_.notify_one(); 186 | return true; 187 | } 188 | 189 | void done() { 190 | { 191 | std::unique_lock lock{mutex_}; 192 | done_ = true; 193 | } 194 | ready_.notify_all(); 195 | } 196 | 197 | template bool try_pop_if_starved(Task &task) { 198 | std::unique_lock lock{mutex_, std::try_to_lock}; 199 | if (!lock || queue_.empty()) 200 | return false; 201 | task = queue_.front(); 202 | const auto now = std::chrono::steady_clock::now(); 203 | const auto diff = std::chrono::duration_cast(now - task.stats_.arrival_time); 204 | if (diff > A::value) { 205 | // pop the task so it can be enqueued at a higher priority 206 | task = std::move(queue_.front()); 207 | queue_.pop_front(); 208 | return true; 209 | } 210 | return false; 211 | } 212 | }; 213 | 214 | } // namespace psched 215 | #pragma once 216 | #include 217 | 218 | namespace psched { 219 | 220 | template struct is_chrono_duration { static constexpr bool value = false; }; 221 | 222 | template 223 | struct is_chrono_duration> { 224 | static constexpr bool value = true; 225 | }; 226 | 227 | template struct task_starvation_after { 228 | static_assert(is_chrono_duration::value, "Duration must be a std::chrono::duration"); 229 | typedef D type; 230 | constexpr static D value = D(P); 231 | }; 232 | 233 | template struct increment_priority_by { constexpr static size_t value = P; }; 234 | 235 | template , class I = increment_priority_by<1>> 236 | struct aging_policy { 237 | typedef T task_starvation_after; 238 | typedef I increment_priority_by; 239 | }; 240 | 241 | } // namespace psched 242 | #pragma once 243 | #include 244 | #include 245 | #include 246 | #include 247 | // #include 248 | // #include 249 | // #include 250 | #include 251 | #include 252 | 253 | namespace psched { 254 | 255 | template struct threads { constexpr static size_t value = T; }; 256 | 257 | template struct priority { constexpr static size_t value = P; }; 258 | 259 | template class PriorityScheduler { 260 | constexpr static size_t priority_levels = queues::number_of_queues; 261 | 262 | std::vector threads_{}; // Scheduler thread pool 263 | std::array, priority_levels> priority_queues_{}; // Array of task queues 264 | std::atomic_bool running_{false}; // Is the scheduler running? 265 | std::mutex mutex_{}; // Mutex to protect `enqueued_` 266 | std::condition_variable ready_{}; // Signal to notify task enqueued 267 | std::atomic_size_t enqueued_{0}; // Incremented when a task is scheduled 268 | 269 | void run() { 270 | while (running_ || enqueued_ > 0) { 271 | // Wait for the `enqueued` signal 272 | { 273 | std::unique_lock lock{mutex_}; 274 | ready_.wait(lock, [this] { return enqueued_ > 0 || !running_; }); 275 | } 276 | 277 | Task t; 278 | 279 | // Handle task starvation at lower priorities 280 | // Modulate priorities based on age 281 | // Start from the lowest priority till (highest_priority - 1) 282 | for (size_t i = 0; i < priority_levels - 1; i++) { 283 | // Check if the front of the queue has a starving task 284 | if (priority_queues_[i] 285 | .template try_pop_if_starved(t)) { 286 | // task has been starved, reschedule at a higher priority 287 | while (running_) { 288 | const auto new_priority = 289 | std::min(i + aging_policy::increment_priority_by::value, priority_levels - 1); 290 | if (priority_queues_[new_priority].try_push(t)) { 291 | break; 292 | } 293 | } 294 | } 295 | } 296 | 297 | // Run the highest priority ready task 298 | bool dequeued = false; 299 | 300 | while (!dequeued) { 301 | for (size_t i = priority_levels; i > 0; --i) { 302 | // Try to pop an item 303 | if (priority_queues_[i - 1].try_pop(t)) { 304 | dequeued = true; 305 | if (enqueued_ > 0) { 306 | enqueued_ -= 1; 307 | } 308 | // execute task 309 | t(); 310 | break; 311 | } 312 | } 313 | if (!(running_ || enqueued_ > 0)) 314 | break; 315 | } 316 | } 317 | } 318 | 319 | public: 320 | PriorityScheduler() { 321 | running_ = true; 322 | for (unsigned n = 0; n != threads::value; ++n) { 323 | threads_.emplace_back([&, n] { run(); }); 324 | } 325 | } 326 | 327 | ~PriorityScheduler() { 328 | running_ = false; 329 | ready_.notify_all(); 330 | for (auto &q : priority_queues_) 331 | q.done(); 332 | for (auto &t : threads_) 333 | if (t.joinable()) 334 | t.join(); 335 | } 336 | 337 | template void schedule(Task &task) { 338 | static_assert(priority::value <= priority_levels, "priority out of range"); 339 | 340 | // Enqueue task 341 | while (running_) { 342 | if (priority_queues_[priority::value].try_push(task)) { 343 | break; 344 | } 345 | } 346 | 347 | // Send `enqueued` signal to worker threads 348 | { 349 | std::unique_lock lock{mutex_}; 350 | enqueued_ += 1; 351 | ready_.notify_one(); 352 | } 353 | } 354 | 355 | void stop() { 356 | running_ = false; 357 | ready_.notify_all(); 358 | for (auto &q : priority_queues_) 359 | q.done(); 360 | for (auto &t : threads_) 361 | if (t.joinable()) 362 | t.join(); 363 | } 364 | }; 365 | 366 | } // namespace psched 367 | -------------------------------------------------------------------------------- /utils/amalgamate/CHANGES.md: -------------------------------------------------------------------------------- 1 | The following changes have been made to the code with respect to : 2 | 3 | - Resolved inspection results from PyCharm: 4 | - replaced tabs with spaces 5 | - added encoding annotation 6 | - reindented file to remove trailing whitespaces 7 | - unused import `sys` 8 | - membership check 9 | - made function from `_is_within` 10 | - removed unused variable `actual_path` 11 | -------------------------------------------------------------------------------- /utils/amalgamate/LICENSE.md: -------------------------------------------------------------------------------- 1 | amalgamate.py - Amalgamate C source and header files 2 | Copyright (c) 2012, Erik Edlund 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Erik Edlund, nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /utils/amalgamate/README.md: -------------------------------------------------------------------------------- 1 | 2 | # amalgamate.py - Amalgamate C source and header files 3 | 4 | Origin: https://bitbucket.org/erikedlund/amalgamate 5 | 6 | Mirror: https://github.com/edlund/amalgamate 7 | 8 | `amalgamate.py` aims to make it easy to use SQLite-style C source and header 9 | amalgamation in projects. 10 | 11 | For more information, please refer to: http://sqlite.org/amalgamation.html 12 | 13 | ## Here be dragons 14 | 15 | `amalgamate.py` is quite dumb, it only knows the bare minimum about C code 16 | required in order to be able to handle trivial include directives. It can 17 | produce weird results for unexpected code. 18 | 19 | Things to be aware of: 20 | 21 | `amalgamate.py` will not handle complex include directives correctly: 22 | 23 | #define HEADER_PATH "path/to/header.h" 24 | #include HEADER_PATH 25 | 26 | In the above example, `path/to/header.h` will not be included in the 27 | amalgamation (HEADER_PATH is never expanded). 28 | 29 | `amalgamate.py` makes the assumption that each source and header file which 30 | is not empty will end in a new-line character, which is not immediately 31 | preceded by a backslash character (see 5.1.1.2p1.2 of ISO C99). 32 | 33 | `amalgamate.py` should be usable with C++ code, but raw string literals from 34 | C++11 will definitely cause problems: 35 | 36 | R"delimiter(Terrible raw \ data " #include )delimiter" 37 | R"delimiter(Terrible raw \ data " escaping)delimiter" 38 | 39 | In the examples above, `amalgamate.py` will stop parsing the raw string literal 40 | when it encounters the first quotation mark, which will produce unexpected 41 | results. 42 | 43 | ## Installing amalgamate.py 44 | 45 | Python v.2.7.0 or higher is required. 46 | 47 | `amalgamate.py` can be tested and installed using the following commands: 48 | 49 | ./test.sh && sudo -k cp ./amalgamate.py /usr/local/bin/ 50 | 51 | ## Using amalgamate.py 52 | 53 | amalgamate.py [-v] -c path/to/config.json -s path/to/source/dir \ 54 | [-p path/to/prologue.(c|h)] 55 | 56 | * The `-c, --config` option should specify the path to a JSON config file which 57 | lists the source files, include paths and where to write the resulting 58 | amalgamation. Have a look at `test/source.c.json` and `test/include.h.json` 59 | to see two examples. 60 | 61 | * The `-s, --source` option should specify the path to the source directory. 62 | This is useful for supporting separate source and build directories. 63 | 64 | * The `-p, --prologue` option should specify the path to a file which will be 65 | added to the beginning of the amalgamation. It is optional. 66 | 67 | -------------------------------------------------------------------------------- /utils/amalgamate/amalgamate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # amalgamate.py - Amalgamate C source and header files. 5 | # Copyright (c) 2012, Erik Edlund 6 | # 7 | # Redistribution and use in source and binary forms, with or without modification, 8 | # are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright notice, 11 | # this list of conditions and the following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # * Neither the name of Erik Edlund, nor the names of its contributors may 18 | # be used to endorse or promote products derived from this software without 19 | # specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 25 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | from __future__ import division 33 | from __future__ import print_function 34 | from __future__ import unicode_literals 35 | 36 | import argparse 37 | import datetime 38 | import json 39 | import os 40 | import re 41 | 42 | 43 | class Amalgamation(object): 44 | 45 | # Prepends self.source_path to file_path if needed. 46 | def actual_path(self, file_path): 47 | if not os.path.isabs(file_path): 48 | file_path = os.path.join(self.source_path, file_path) 49 | return file_path 50 | 51 | # Search included file_path in self.include_paths and 52 | # in source_dir if specified. 53 | def find_included_file(self, file_path, source_dir): 54 | search_dirs = self.include_paths[:] 55 | if source_dir: 56 | search_dirs.insert(0, source_dir) 57 | 58 | for search_dir in search_dirs: 59 | search_path = os.path.join(search_dir, file_path) 60 | if os.path.isfile(self.actual_path(search_path)): 61 | return search_path 62 | return None 63 | 64 | def __init__(self, args): 65 | with open(args.config, 'r') as f: 66 | config = json.loads(f.read()) 67 | for key in config: 68 | setattr(self, key, config[key]) 69 | 70 | self.verbose = args.verbose == "yes" 71 | self.prologue = args.prologue 72 | self.source_path = args.source_path 73 | self.included_files = [] 74 | 75 | # Generate the amalgamation and write it to the target file. 76 | def generate(self): 77 | amalgamation = "" 78 | 79 | if self.prologue: 80 | with open(self.prologue, 'r') as f: 81 | amalgamation += datetime.datetime.now().strftime(f.read()) 82 | 83 | if self.verbose: 84 | print("Config:") 85 | print(" target = {0}".format(self.target)) 86 | print(" working_dir = {0}".format(os.getcwd())) 87 | print(" include_paths = {0}".format(self.include_paths)) 88 | print("Creating amalgamation:") 89 | for file_path in self.sources: 90 | # Do not check the include paths while processing the source 91 | # list, all given source paths must be correct. 92 | # actual_path = self.actual_path(file_path) 93 | print(" - processing \"{0}\"".format(file_path)) 94 | t = TranslationUnit(file_path, self, True) 95 | amalgamation += t.content 96 | 97 | with open(self.target, 'w') as f: 98 | f.write(amalgamation) 99 | 100 | print("...done!\n") 101 | if self.verbose: 102 | print("Files processed: {0}".format(self.sources)) 103 | print("Files included: {0}".format(self.included_files)) 104 | print("") 105 | 106 | 107 | def _is_within(match, matches): 108 | for m in matches: 109 | if match.start() > m.start() and \ 110 | match.end() < m.end(): 111 | return True 112 | return False 113 | 114 | 115 | class TranslationUnit(object): 116 | # // C++ comment. 117 | cpp_comment_pattern = re.compile(r"//.*?\n") 118 | 119 | # /* C comment. */ 120 | c_comment_pattern = re.compile(r"/\*.*?\*/", re.S) 121 | 122 | # "complex \"stri\\\ng\" value". 123 | string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S) 124 | 125 | # Handle simple include directives. Support for advanced 126 | # directives where macros and defines needs to expanded is 127 | # not a concern right now. 128 | include_pattern = re.compile( 129 | r'#\s*include\s+(<|")(?P.*?)("|>)', re.S) 130 | 131 | # #pragma once 132 | pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S) 133 | 134 | # Search for pattern in self.content, add the match to 135 | # contexts if found and update the index accordingly. 136 | def _search_content(self, index, pattern, contexts): 137 | match = pattern.search(self.content, index) 138 | if match: 139 | contexts.append(match) 140 | return match.end() 141 | return index + 2 142 | 143 | # Return all the skippable contexts, i.e., comments and strings 144 | def _find_skippable_contexts(self): 145 | # Find contexts in the content in which a found include 146 | # directive should not be processed. 147 | skippable_contexts = [] 148 | 149 | # Walk through the content char by char, and try to grab 150 | # skippable contexts using regular expressions when found. 151 | i = 1 152 | content_len = len(self.content) 153 | while i < content_len: 154 | j = i - 1 155 | current = self.content[i] 156 | previous = self.content[j] 157 | 158 | if current == '"': 159 | # String value. 160 | i = self._search_content(j, self.string_pattern, 161 | skippable_contexts) 162 | elif current == '*' and previous == '/': 163 | # C style comment. 164 | i = self._search_content(j, self.c_comment_pattern, 165 | skippable_contexts) 166 | elif current == '/' and previous == '/': 167 | # C++ style comment. 168 | i = self._search_content(j, self.cpp_comment_pattern, 169 | skippable_contexts) 170 | else: 171 | # Skip to the next char. 172 | i += 1 173 | 174 | return skippable_contexts 175 | 176 | # Returns True if the match is within list of other matches 177 | 178 | # Removes pragma once from content 179 | def _process_pragma_once(self): 180 | content_len = len(self.content) 181 | if content_len < len("#include "): 182 | return 0 183 | 184 | # Find contexts in the content in which a found include 185 | # directive should not be processed. 186 | skippable_contexts = self._find_skippable_contexts() 187 | 188 | pragmas = [] 189 | pragma_once_match = self.pragma_once_pattern.search(self.content) 190 | while pragma_once_match: 191 | if not _is_within(pragma_once_match, skippable_contexts): 192 | pragmas.append(pragma_once_match) 193 | 194 | pragma_once_match = self.pragma_once_pattern.search(self.content, 195 | pragma_once_match.end()) 196 | 197 | # Handle all collected pragma once directives. 198 | prev_end = 0 199 | tmp_content = '' 200 | for pragma_match in pragmas: 201 | tmp_content += self.content[prev_end:pragma_match.start()] 202 | prev_end = pragma_match.end() 203 | tmp_content += self.content[prev_end:] 204 | self.content = tmp_content 205 | 206 | # Include all trivial #include directives into self.content. 207 | def _process_includes(self): 208 | content_len = len(self.content) 209 | if content_len < len("#include "): 210 | return 0 211 | 212 | # Find contexts in the content in which a found include 213 | # directive should not be processed. 214 | skippable_contexts = self._find_skippable_contexts() 215 | 216 | # Search for include directives in the content, collect those 217 | # which should be included into the content. 218 | includes = [] 219 | include_match = self.include_pattern.search(self.content) 220 | while include_match: 221 | if not _is_within(include_match, skippable_contexts): 222 | include_path = include_match.group("path") 223 | search_same_dir = include_match.group(1) == '"' 224 | found_included_path = self.amalgamation.find_included_file( 225 | include_path, self.file_dir if search_same_dir else None) 226 | if found_included_path: 227 | includes.append((include_match, found_included_path)) 228 | 229 | include_match = self.include_pattern.search(self.content, 230 | include_match.end()) 231 | 232 | # Handle all collected include directives. 233 | prev_end = 0 234 | tmp_content = '' 235 | for include in includes: 236 | include_match, found_included_path = include 237 | tmp_content += self.content[prev_end:include_match.start()] 238 | tmp_content += "// {0}".format(include_match.group(0)) 239 | if found_included_path not in self.amalgamation.included_files: 240 | t = TranslationUnit(found_included_path, self.amalgamation, False) 241 | tmp_content += t.content 242 | prev_end = include_match.end() 243 | tmp_content += self.content[prev_end:] 244 | self.content = tmp_content 245 | 246 | return len(includes) 247 | 248 | # Make all content processing 249 | def _process(self): 250 | if not self.is_root: 251 | self._process_pragma_once() 252 | self._process_includes() 253 | 254 | def __init__(self, file_path, amalgamation, is_root): 255 | self.file_path = file_path 256 | self.file_dir = os.path.dirname(file_path) 257 | self.amalgamation = amalgamation 258 | self.is_root = is_root 259 | 260 | self.amalgamation.included_files.append(self.file_path) 261 | 262 | actual_path = self.amalgamation.actual_path(file_path) 263 | if not os.path.isfile(actual_path): 264 | raise IOError("File not found: \"{0}\"".format(file_path)) 265 | with open(actual_path, 'r') as f: 266 | self.content = f.read() 267 | self._process() 268 | 269 | 270 | def main(): 271 | description = "Amalgamate C source and header files." 272 | usage = " ".join([ 273 | "amalgamate.py", 274 | "[-v]", 275 | "-c path/to/config.json", 276 | "-s path/to/source/dir", 277 | "[-p path/to/prologue.(c|h)]" 278 | ]) 279 | argsparser = argparse.ArgumentParser( 280 | description=description, usage=usage) 281 | 282 | argsparser.add_argument("-v", "--verbose", dest="verbose", 283 | choices=["yes", "no"], metavar="", help="be verbose") 284 | 285 | argsparser.add_argument("-c", "--config", dest="config", 286 | required=True, metavar="", help="path to a JSON config file") 287 | 288 | argsparser.add_argument("-s", "--source", dest="source_path", 289 | required=True, metavar="", help="source code path") 290 | 291 | argsparser.add_argument("-p", "--prologue", dest="prologue", 292 | required=False, metavar="", help="path to a C prologue file") 293 | 294 | amalgamation = Amalgamation(argsparser.parse_args()) 295 | amalgamation.generate() 296 | 297 | 298 | if __name__ == "__main__": 299 | main() -------------------------------------------------------------------------------- /utils/amalgamate/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "cgame competitive programming for codingame", 3 | "target": "AllTrees.cpp", 4 | "sources": [ 5 | "test/gametheory/TreesTest.cpp" 6 | ], 7 | "include_paths": ["include"] 8 | } 9 | --------------------------------------------------------------------------------