├── db ├── db_test.py ├── main.cpp ├── Makefile └── Db.py ├── printutil ├── PrintUtil.cpp ├── PrintUtil.py ├── main.cpp ├── PrintUtil_macros.cpp ├── Makefile └── PrintUtil.hpp ├── adder ├── Adder.py ├── main.cpp └── Makefile ├── README.md ├── run_hpx.cpp └── comp.py /db/db_test.py: -------------------------------------------------------------------------------- 1 | import db 2 | 3 | db.start(3) 4 | 5 | d = db.Db() 6 | d.add("foo") 7 | d.add_vec(["apple","baz"]) 8 | for s in ["apple","pear","foo","baz","bar"]: 9 | print("%7s -> %s" % (s,d.contains(s))) 10 | 11 | db.stop() 12 | -------------------------------------------------------------------------------- /printutil/PrintUtil.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace printutil { namespace server { 4 | 5 | PrintUtil::~PrintUtil() {} 6 | 7 | void PrintUtil::hello2() { 8 | std::cout << "Hello2" << std::endl; 9 | } 10 | 11 | }} 12 | -------------------------------------------------------------------------------- /adder/Adder.py: -------------------------------------------------------------------------------- 1 | from comp import * 2 | 3 | @Component(namespace='adder') 4 | class Adder: 5 | 6 | counter : int = 0 7 | values : smap[str, int] = default 8 | 9 | def get(self) -> int: 10 | "return counter;" 11 | 12 | def get_value(self, key : str) -> int: 13 | "return values[key];" 14 | 15 | def add(self, val : int) -> None: 16 | "counter += val;" 17 | 18 | def add_value(self, key : str, val : int) -> None: 19 | "values[key] += val;" 20 | -------------------------------------------------------------------------------- /printutil/PrintUtil.py: -------------------------------------------------------------------------------- 1 | from comp import * 2 | 3 | @Component(namespace='printutil',pybind11='prutil') 4 | class PrintUtil: 5 | def hello(self) -> None: 6 | 'std::cout << "Hello" << std::endl;' 7 | def hello2(self) -> None: 8 | pass 9 | def write(self, s : str) -> None: 10 | 'std::cout << s << std::endl;' 11 | def one(self) -> int: 12 | "return 1;" 13 | def __init__(self): 14 | "/*comment*/" 15 | def __del__(self): 16 | pass 17 | -------------------------------------------------------------------------------- /printutil/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using printutil::PrintUtil; 5 | 6 | int hpx_main(int argc,char **argv) 7 | { 8 | auto loc = hpx::find_here(); 9 | 10 | PrintUtil put(hpx::components::new_(loc)); 11 | put.hello().wait(); 12 | put.hello2().wait(); 13 | put.write("This is a message").wait(); 14 | return hpx::finalize(); 15 | } 16 | 17 | int main(int argc,char **argv) 18 | { 19 | return hpx::init(argc, argv); 20 | } 21 | -------------------------------------------------------------------------------- /adder/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using adder::Adder; 5 | 6 | int hpx_main(int argc,char **argv) 7 | { 8 | auto loc = hpx::find_here(); 9 | 10 | Adder ad(hpx::components::new_(loc)); 11 | std::cout << ad.get().get() << std::endl; 12 | ad.add(3).wait(); 13 | std::cout << ad.get().get() << std::endl; 14 | ad.add_value("fish",4).wait(); 15 | std::cout << ad.get_value("fish").get() << std::endl; 16 | return hpx::finalize(); 17 | } 18 | 19 | int main(int argc,char **argv) 20 | { 21 | return hpx::init(argc, argv); 22 | } 23 | -------------------------------------------------------------------------------- /adder/Makefile: -------------------------------------------------------------------------------- 1 | HPX_LIB_DIR=/usr/local/lib64 2 | 3 | run: main 4 | LD_LIBRARY_PATH=.:$(HPX_LIB_DIR) ./main 5 | 6 | Adder.hpp Adder_macros.cpp : Adder.py 7 | python3 Adder.py 8 | 9 | Adder_macros.o : Adder_macros.cpp Adder.hpp 10 | hpxcxx -fPIC -g -c -I. Adder_macros.cpp 11 | 12 | main.o : main.cpp Adder.hpp 13 | hpxcxx -g -c -I. main.cpp 14 | 15 | libAdder.so : Adder_macros.o 16 | hpxcxx -g -fPIC --comp=Adder Adder_macros.o 17 | 18 | main : libAdder.so main.o 19 | hpxcxx -g -fPIC --exe=main libAdder.so main.o 20 | 21 | clean : polish 22 | rm -fr main *.o *.so Adder.hpp Adder_macros.cpp 23 | 24 | polish: 25 | rm -fr hpx.*.log core.* 26 | -------------------------------------------------------------------------------- /db/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using database::Db; 5 | 6 | int hpx_main(int argc,char **argv) 7 | { 8 | auto loc = hpx::find_here(); 9 | 10 | Db db(hpx::components::new_(loc)); 11 | 12 | db.add("hello").wait(); 13 | db.add("world").wait(); 14 | 15 | std::vector vec{"apple","orange","pear"}; 16 | db.add_vec(vec).wait(); 17 | 18 | std::cout << db.contains("hello").get() << std::endl; 19 | std::cout << db.contains("goodbye").get() << std::endl; 20 | std::cout << db.contains("pear").get() << std::endl; 21 | 22 | return hpx::finalize(); 23 | } 24 | 25 | int main(int argc,char **argv) 26 | { 27 | return hpx::init(argc, argv); 28 | } 29 | -------------------------------------------------------------------------------- /printutil/PrintUtil_macros.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | HPX_REGISTER_COMPONENT_MODULE(); 3 | 4 | typedef hpx::components::component< 5 | printutil::server::PrintUtil 6 | > PrintUtil_type; 7 | 8 | HPX_REGISTER_COMPONENT(PrintUtil_type, PrintUtil); 9 | 10 | HPX_REGISTER_ACTION( 11 | printutil::server::PrintUtil::hello_action, PrintUtil_hello_action); 12 | 13 | HPX_REGISTER_ACTION( 14 | printutil::server::PrintUtil::hello2_action, PrintUtil_hello2_action); 15 | 16 | HPX_REGISTER_ACTION( 17 | printutil::server::PrintUtil::one_action, PrintUtil_one_action); 18 | 19 | HPX_REGISTER_ACTION( 20 | printutil::server::PrintUtil::write_action, PrintUtil_write_action); 21 | 22 | -------------------------------------------------------------------------------- /db/Makefile: -------------------------------------------------------------------------------- 1 | HPX_LIB_DIR=/usr/local/lib64 2 | 3 | run: main db.so 4 | LD_LIBRARY_PATH=.:$(HPX_LIB_DIR) ./main 5 | LD_LIBRARY_PATH=.:$(HPX_LIB_DIR) python3 db_test.py 6 | 7 | db.so : db_py11.o libDb.so 8 | g++ -g -fPIC -shared -o db.so db_py11.o libDb.so 9 | 10 | db_py11.o : db_py11.cpp Db.hpp 11 | hpxcxx -I.. -g -fPIC -c -I/usr/include/python3.6m -I. db_py11.cpp 12 | 13 | Db.hpp Db_macros.cpp : Db.py 14 | python3 Db.py 15 | 16 | Db_macros.o : Db_macros.cpp Db.hpp 17 | hpxcxx -fPIC -g -c -I. Db_macros.cpp 18 | 19 | main.o : main.cpp Db.hpp 20 | hpxcxx -g -c -I. main.cpp 21 | 22 | libDb.so : Db_macros.o 23 | hpxcxx -g -fPIC --comp=Db Db_macros.o 24 | 25 | main : libDb.so main.o 26 | hpxcxx -g -fPIC --exe=main libDb.so main.o 27 | 28 | clean : polish 29 | rm -fr main *.o *.so Db.hpp Db_macros.cpp 30 | 31 | polish: 32 | rm -fr hpx.*.log core.* 33 | -------------------------------------------------------------------------------- /db/Db.py: -------------------------------------------------------------------------------- 1 | from comp import * 2 | 3 | # Note that svec and Bool exist in comp, but we 4 | # add these types here to show how the create type 5 | # mechanism works. 6 | create_type("myvec",alt="std::vector",is_template=True) 7 | create_type("bool") 8 | 9 | @Component(namespace='database',headers=['algorithm'],pybind11='db') 10 | class Db: 11 | 12 | table : myvec[str] = default 13 | 14 | # This method is not exposed on the client 15 | @server_only 16 | def sort_me(self) -> None: 17 | 'std::sort(table.begin(), table.end());' 18 | 19 | def add(self, entry : str) -> None: 20 | """ 21 | table.push_back(entry); 22 | sort_me(); 23 | """ 24 | 25 | def add_vec(self, entries : Ref(Const(myvec[str]))) -> None: 26 | """ 27 | for(auto i=entries.begin();i != entries.end();++i) 28 | table.push_back(*i); 29 | sort_me(); 30 | """ 31 | 32 | def contains(self, entry : str) -> bool: 33 | "return std::binary_search(table.begin(), table.end(), entry);" 34 | -------------------------------------------------------------------------------- /printutil/Makefile: -------------------------------------------------------------------------------- 1 | HPX_LIB_DIR=/usr/local/lib64 2 | 3 | run: main 4 | LD_LIBRARY_PATH=.:$(HPX_LIB_DIR) ./main 5 | 6 | py: prutil.so 7 | LD_LIBRARY_PATH=.:$(HPX_LIB_DIR) python3 test.py 8 | 9 | PrintUtil.hpp PrintUtil_macros.cpp : PrintUtil.py 10 | python3 PrintUtil.py 11 | 12 | PrintUtil.o : PrintUtil.cpp PrintUtil.hpp 13 | hpxcxx -fPIC -g -c -I. PrintUtil.cpp 14 | 15 | PrintUtil_macros.o : PrintUtil_macros.cpp PrintUtil.hpp 16 | hpxcxx -fPIC -g -c -I. PrintUtil_macros.cpp 17 | 18 | main.o : main.cpp PrintUtil.hpp 19 | hpxcxx -g -c -I. main.cpp 20 | 21 | libPrintUtil.so : PrintUtil.o PrintUtil_macros.o 22 | hpxcxx -g -fPIC --comp=PrintUtil PrintUtil.o PrintUtil_macros.o 23 | 24 | main : libPrintUtil.so main.o 25 | hpxcxx -g -fPIC --exe=main libPrintUtil.so main.o PrintUtil.o 26 | 27 | prutil.so : prutil_py11.cpp libPrintUtil.so init_globally.cpp 28 | g++ -DHPX_DEBUG -g -fPIC -I. -I/usr/include/python3.6m -shared -o prutil.so prutil_py11.cpp libPrintUtil.so 29 | 30 | clean : polish 31 | rm -fr main *.o *.so PrintUtil.hpp 32 | 33 | polish: 34 | rm -fr hpx.*.log core.* 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The HPX Component Maker 2 | 3 | The idea behind this project is to write a simple Python class definition 4 | and have it generate all the necessary boilerplate for a full HPX component. 5 | 6 | ## A first example... 7 | 8 | Suppose we create a class named `PrintUtil.py` as follows: 9 | ``` 10 | from comp import * 11 | 12 | @Component(namespace='printutil') 13 | class PrintUtil: 14 | def hello(self) -> None: 15 | cplusplus('std::cout << "Hello" << std::endl;') 16 | def hello2() -> None: 17 | pass 18 | def write(self, s : str) -> None: 19 | cplusplus('std::cout << s << std::endl;') 20 | def __init__(self): 21 | cplusplus("/*comment*/") 22 | def __del__(self): 23 | pass 24 | ``` 25 | By using the magic of Python Decorators, running `Python PrintUtil.py` will generate the following two class files: 26 | 27 | The first file is 28 | [`PrintUtil.hpp`](https://github.com/stevenrbrandt/HPXCompMaker/blob/master/printutil/PrintUtil.hpp) 29 | 30 | The second file is 31 | [`PrintUtil_macros.hpp`](https://github.com/stevenrbrandt/HPXCompMaker/blob/master/printutil/PrintUtil_macros.cpp) 32 | 33 | Note that all the Python types map to corresponding C++ types. `None` maps to 34 | `Void`, `str` maps to `std::string`, and so on in a mostly intuitive manner. 35 | 36 | Python's `__init__` function will become a constructor. In this case, it's an 37 | empty constructor with a single comment inside. 38 | 39 | Python's `__del__` function will become a destructor. In this case, it's 40 | unimplemented. A separate C++ file will be required to provide its 41 | implementation. 42 | 43 | Likewise, the `hello2()` function, since it has no body, must be provided. 44 | A file where those functions were provided by hand may be found here: 45 | [`PrintUtil.cpp`](https://github.com/stevenrbrandt/HPXCompMaker/blob/master/printutil/PrintUtil.cpp) 46 | 47 | See the printutil directory for a working Makefile and code example. 48 | 49 | ## A second example: Adder 50 | 51 | Next, let us consider creating a component with state. 52 | 53 | ``` 54 | from comp import * 55 | 56 | @Component(namespace='adder') 57 | class Adder: 58 | 59 | counter : int = 0 60 | values : smap[str, int] = default 61 | 62 | def get(self) -> int: 63 | cplusplus("return counter;") 64 | 65 | def get_value(self, key : str) -> int: 66 | cplusplus("return values[key];") 67 | 68 | def add(self, val : int) -> None: 69 | cplusplus("counter += val;") 70 | 71 | def add_value(self, key : str, val : int) -> None: 72 | cplusplus("values[key] += val;") 73 | ``` 74 | 75 | In this example, our component has two fields: counter, and values. 76 | For values, we have type `smap[str,int]` which maps onto 77 | `std::map`. Note that we need to initialize it with 78 | `default`, otherwise, our generated code will try to initialize it 79 | with `nullptr`. 80 | 81 | See the adder directory for a Makefile and working code example. 82 | -------------------------------------------------------------------------------- /printutil/PrintUtil.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRINTUTIL__PRINTUTIL_HPP 2 | #define PRINTUTIL__PRINTUTIL_HPP 1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace printutil { 11 | namespace server { 12 | 13 | struct HPX_COMPONENT_EXPORT PrintUtil 14 | : hpx::components::component_base 15 | { 16 | /** 17 | * All internal state. 18 | */ 19 | 20 | /* Functions */ 21 | 22 | 23 | ~PrintUtil(); 24 | 25 | PrintUtil() { /*comment*/ } 26 | 27 | void hello() { std::cout << "Hello" << std::endl; } 28 | HPX_DEFINE_COMPONENT_ACTION(printutil::server::PrintUtil, hello); 29 | 30 | void hello2(); 31 | HPX_DEFINE_COMPONENT_ACTION(printutil::server::PrintUtil, hello2); 32 | 33 | int one() { return 1; } 34 | HPX_DEFINE_COMPONENT_ACTION(printutil::server::PrintUtil, one); 35 | 36 | void write(std::string s) { std::cout << s << std::endl; } 37 | HPX_DEFINE_COMPONENT_ACTION(printutil::server::PrintUtil, write); 38 | }; // end server code 39 | 40 | } // end server namespace 41 | 42 | } // namespace printutil 43 | 44 | HPX_REGISTER_ACTION_DECLARATION( 45 | printutil::server::PrintUtil::hello_action, printutil::server::PrintUtil); 46 | HPX_REGISTER_ACTION_DECLARATION( 47 | printutil::server::PrintUtil::hello2_action, printutil::server::PrintUtil); 48 | HPX_REGISTER_ACTION_DECLARATION( 49 | printutil::server::PrintUtil::one_action, printutil::server::PrintUtil); 50 | HPX_REGISTER_ACTION_DECLARATION( 51 | printutil::server::PrintUtil::write_action, printutil::server::PrintUtil); 52 | 53 | namespace printutil { 54 | 55 | struct PrintUtil : hpx::components::client_base 56 | { 57 | typedef hpx::components::client_base base_type; 58 | 59 | PrintUtil(hpx::future && f) 60 | : base_type(std::move(f)) 61 | {} 62 | 63 | PrintUtil(hpx::naming::id_type && f) 64 | : base_type(std::move(f)) 65 | {} 66 | 67 | ~PrintUtil(){} 68 | 69 | 70 | 71 | hpx::future hello() { 72 | return hpx::async(this->get_id()); 73 | } 74 | static hpx::future hello(hpx::naming::id_type& this_id) { 75 | return hpx::async(this_id); 76 | } 77 | 78 | hpx::future hello2() { 79 | return hpx::async(this->get_id()); 80 | } 81 | static hpx::future hello2(hpx::naming::id_type& this_id) { 82 | return hpx::async(this_id); 83 | } 84 | 85 | hpx::future one() { 86 | return hpx::async(this->get_id()); 87 | } 88 | static hpx::future one(hpx::naming::id_type& this_id) { 89 | return hpx::async(this_id); 90 | } 91 | 92 | hpx::future write(std::string s) { 93 | return hpx::async(this->get_id(),s); 94 | } 95 | static hpx::future write(hpx::naming::id_type& this_id,std::string s) { 96 | return hpx::async(this_id,s); 97 | } 98 | }; // end client code 99 | 100 | } // namespace printutil 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /run_hpx.cpp: -------------------------------------------------------------------------------- 1 | #ifndef RUN_HPX_CPP 2 | #define RUN_HPX_CPP 3 | // Copyright (c) 2016 Hartmut Kaiser 4 | // 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | 8 | // This example demonstrates several things: 9 | // 10 | // - How to initialize (and terminate) the HPX runtime from a global object 11 | // (see the type `manage_global_runtime' below) 12 | // - How to register and unregister any (kernel) thread with the HPX runtime 13 | // - How to launch an HPX thread from any (registered) kernel thread and 14 | // how to wait for the HPX thread to run to completion before continuing. 15 | // Any value returned from the HPX thread will be marshaled back to the 16 | // calling (kernel) thread. 17 | // 18 | // This scheme is generally useful if HPX should be initialized from a shared 19 | // library and the main executable might not even be aware of this. 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace hpx_global { 31 | 32 | /////////////////////////////////////////////////////////////////////////////// 33 | // Store the command line arguments in global variables to make them available 34 | // to the startup code. 35 | 36 | #if defined(linux) || defined(__linux) || defined(__linux__) 37 | 38 | int __argc = 1; 39 | char __arg0__[] { "jupytercling" }; 40 | char* __argv__[] {(char*)__arg0__,0}; 41 | char** __argv = (char**)__argv__; 42 | 43 | void set_argv_argv(int argc, char* argv[], char* env[]) 44 | { 45 | //__argc = argc; 46 | //__argv = argv; 47 | } 48 | 49 | __attribute__((section(".init_array"))) 50 | void (*set_global_argc_argv)(int, char*[], char*[]) = &set_argv_argv; 51 | 52 | #elif defined(__APPLE__) 53 | 54 | #include 55 | 56 | inline int get_arraylen(char** argv) 57 | { 58 | int count = 0; 59 | if (nullptr != argv) 60 | { 61 | while(nullptr != argv[count]) 62 | ++count; 63 | } 64 | return count; 65 | } 66 | 67 | #error "Bad" 68 | int __argc = get_arraylen(*_NSGetArgv()); 69 | char** __argv = *_NSGetArgv(); 70 | 71 | #endif 72 | 73 | /////////////////////////////////////////////////////////////////////////////// 74 | // This class demonstrates how to initialize a console instance of HPX 75 | // (locality 0). In order to create an HPX instance which connects to a running 76 | // HPX application two changes have to be made: 77 | // 78 | // - replace hpx::runtime_mode_console with hpx::runtime_mode_connect 79 | // - replace hpx::finalize() with hpx::disconnect() 80 | // 81 | struct manage_global_runtime 82 | { 83 | manage_global_runtime(int nth) 84 | : running_(false), rts_(nullptr) 85 | { 86 | #if defined(HPX_WINDOWS) 87 | hpx::detail::init_winsocket(); 88 | #endif 89 | 90 | std::ostringstream thread_spec; 91 | thread_spec << "--hpx:threads=" << nth; 92 | std::vector const cfg = { 93 | // make sure hpx_main is always executed 94 | "hpx.run_hpx_main!=1", 95 | thread_spec.str().c_str(), 96 | // allow for unknown command line options 97 | "hpx.commandline.allow_unknown!=1", 98 | // disable HPX' short options 99 | "hpx.commandline.aliasing!=0", 100 | "hpx.stacks.small_size=0x160000" 101 | }; 102 | 103 | using hpx::util::placeholders::_1; 104 | using hpx::util::placeholders::_2; 105 | hpx::util::function_nonser start_function = 106 | hpx::util::bind(&manage_global_runtime::hpx_main, this, _1, _2); 107 | 108 | if (!hpx::start(start_function, __argc, __argv, cfg, hpx::runtime_mode_console)) 109 | { 110 | // Something went wrong while initializing the runtime. 111 | // This early we can't generate any output, just bail out. 112 | std::abort(); 113 | } 114 | 115 | // Wait for the main HPX thread (hpx_main below) to have started running 116 | std::unique_lock lk(startup_mtx_); 117 | while (!running_) 118 | startup_cond_.wait(lk); 119 | } 120 | 121 | ~manage_global_runtime() 122 | { 123 | // notify hpx_main above to tear down the runtime 124 | { 125 | std::lock_guard lk(mtx_); 126 | rts_ = nullptr; // reset pointer 127 | } 128 | 129 | cond_.notify_one(); // signal exit 130 | 131 | // wait for the runtime to exit 132 | hpx::stop(); 133 | } 134 | 135 | // registration of external (to HPX) threads 136 | void register_thread(const char *name) 137 | { 138 | hpx::register_thread(rts_, name); 139 | } 140 | void unregister_thread() 141 | { 142 | hpx::unregister_thread(rts_); 143 | } 144 | 145 | protected: 146 | // Main HPX thread, does nothing but wait for the application to exit 147 | int hpx_main(int argc, char* argv[]) 148 | { 149 | // Store a pointer to the runtime here. 150 | rts_ = hpx::get_runtime_ptr(); 151 | 152 | // Signal to constructor that thread has started running. 153 | { 154 | std::lock_guard lk(startup_mtx_); 155 | running_ = true; 156 | } 157 | 158 | startup_cond_.notify_one(); 159 | 160 | // Here other HPX specific functionality could be invoked... 161 | 162 | // Now, wait for destructor to be called. 163 | { 164 | std::unique_lock lk(mtx_); 165 | if (rts_ != nullptr) 166 | cond_.wait(lk); 167 | } 168 | 169 | // tell the runtime it's ok to exit 170 | return hpx::finalize(); 171 | } 172 | 173 | private: 174 | hpx::lcos::local::spinlock mtx_; 175 | hpx::lcos::local::condition_variable_any cond_; 176 | 177 | std::mutex startup_mtx_; 178 | std::condition_variable startup_cond_; 179 | bool running_; 180 | 181 | hpx::runtime* rts_; 182 | }; 183 | 184 | // This global object will initialize HPX in its constructor and make sure HPX 185 | // stops running in its destructor. 186 | manage_global_runtime *init = nullptr;//new manage_global_runtime;//nullptr; 187 | 188 | int next_id_seq = 1; 189 | //char next_id_buf[5]; 190 | std::string next_id() { 191 | //sprintf(next_id_buf,"id=%d",next_id_seq++); 192 | std::ostringstream buf; 193 | buf << "id=" << next_id_seq++; 194 | return std::move(buf.str()); 195 | } 196 | 197 | /////////////////////////////////////////////////////////////////////////////// 198 | struct thread_registration_wrapper 199 | { 200 | thread_registration_wrapper(const char *name) 201 | { 202 | // Register this thread with HPX, this should be done once for 203 | // each external OS-thread intended to invoke HPX functionality. 204 | // Calling this function more than once will silently fail (will 205 | // return false). 206 | init->register_thread(name); 207 | } 208 | ~thread_registration_wrapper() 209 | { 210 | // Unregister the thread from HPX, this should be done once in the 211 | // end before the external thread exists. 212 | init->unregister_thread(); 213 | } 214 | }; 215 | 216 | /////////////////////////////////////////////////////////////////////////////// 217 | 218 | 219 | HPX_EXPORT void start_runtime(int nth) { 220 | if(init == nullptr) 221 | init = new manage_global_runtime(nth); 222 | } 223 | 224 | HPX_EXPORT void stop_runtime() { 225 | delete init; 226 | init = nullptr; 227 | } 228 | 229 | void submit_work(int nth,std::function work_item) { 230 | 231 | start_runtime(nth); 232 | 233 | std::string id = next_id(); 234 | std::function hpx_work_item = [&](){ 235 | thread_registration_wrapper register_thread(id.c_str()); 236 | hpx::threads::run_as_hpx_thread(work_item); 237 | }; 238 | std::thread t(hpx_work_item); 239 | t.join(); 240 | 241 | stop_runtime(); 242 | 243 | } 244 | 245 | }; 246 | 247 | const int run_hpx_threads = 4; 248 | 249 | template 250 | HPX_EXPORT typename std::result_of::type run_hpx(F f) { 251 | if constexpr(std::is_same::type,void>::value) { 252 | hpx_global::submit_work(run_hpx_threads, f); 253 | } else { 254 | typename std::result_of::type r; 255 | hpx_global::submit_work(run_hpx_threads, [&r,&f](){ 256 | r = f(); 257 | }); 258 | return std::move(r); 259 | } 260 | } 261 | #endif 262 | -------------------------------------------------------------------------------- /comp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import inspect 3 | import ast 4 | from typing import List, Dict, NewType 5 | import numpy as np 6 | import re 7 | from functools import partial 8 | 9 | class FuncDesc: 10 | """ 11 | Describe a function: it's name, arguments 12 | and return type. 13 | """ 14 | def __init__(self,name,args,rettype,body=None): 15 | self.name = name 16 | self.rettype = rettype 17 | self.args = args 18 | self.body = body 19 | def __str__(self): 20 | s = "" 21 | s += self.name 22 | s += "(" 23 | sep = "" 24 | for arg, type in self.args: 25 | s += sep + arg+": "+type 26 | sep = ", " 27 | s += ") -> "+self.rettype 28 | return s 29 | 30 | def get_decl_args(func): 31 | result = '' 32 | sep = '' 33 | for argname, argtype in func.args: 34 | result += "%s%s %s" % (sep, ttran(argtype), argname) 35 | sep = ',' 36 | return result 37 | 38 | def get_args(func): 39 | result = '' 40 | sep = '' 41 | for argname, argtype in func.args: 42 | result += "%s%s" % (sep, argname) 43 | sep = ',' 44 | return result 45 | 46 | class ComponentDesc: 47 | """ 48 | Describe a component, including 49 | the namespace it lives in, the headers 50 | it needs, and the fields and functions 51 | it requires. 52 | """ 53 | def __init__(self,cname): 54 | self.headers = [] 55 | self.cname = cname 56 | self.namespace = None 57 | self.funcs = {} 58 | self.fields = {} 59 | def addfield(self,typef,namef,valf): 60 | self.fields[namef] = (typef, valf) 61 | def addfunc(self,func): 62 | self.funcs[func.name] = func 63 | def setret(self,rettype): 64 | self.rettype = rettype 65 | def setnamespace(self,namespace): 66 | self.namespace = namespace 67 | def createboilerplate(self): 68 | pass 69 | 70 | def server_name(self): 71 | if self.namespace is None: 72 | return "server::"+self.cname 73 | else: 74 | return self.namespace+"::server::"+self.cname 75 | 76 | def full_name(self): 77 | if self.namespace is None: 78 | return self.cname 79 | else: 80 | return self.namespace+"::"+self.cname 81 | 82 | def __str__(self): 83 | s = "" 84 | s += "component: "+self.cname+"\n" 85 | for f in self.fields.keys(): 86 | t, v = self.fields[f] 87 | s += " %s %s = %s;\n" % (t,f,v) 88 | s += '\n' 89 | for f in self.funcs.keys(): 90 | s += " " + str(self.funcs[f]) + "\n" 91 | return s 92 | 93 | def getval(ty): 94 | """ 95 | Return a value, e.g. a specific number or string 96 | from an AST element. 97 | """ 98 | t = type(ty) 99 | if ty is None: 100 | return None 101 | elif t == int: 102 | return str(ty) 103 | elif t == ast.NameConstant: 104 | return ty.value 105 | elif t == ast.Name: 106 | return ty.id 107 | elif t == ast.Num: 108 | return str(ty.n) 109 | elif t == ast.USub: 110 | return "-" 111 | elif t == ast.UnaryOp: 112 | return getval(ty.op)+getval(ty.operand) 113 | elif t == ast.Str: 114 | s = re.sub(r'"',r'\"',ty.s) 115 | s = re.sub('\n',r'\\n',s) 116 | return '"'+s+'"' 117 | else: 118 | print(t,ty,dir(ty)) 119 | raise Exception(ty) 120 | 121 | def gettype(ty): 122 | """ 123 | Extract a C++ type from an AST element. 124 | """ 125 | global type_names 126 | if ty is None: 127 | return "None" 128 | t = type(ty) 129 | if t == ast.Name: 130 | if ty.id in type_names.keys(): 131 | return type_names[ty.id] 132 | return ty.id 133 | elif t in [np.float64]: 134 | return str(t) 135 | elif t in [ast.Index, ast.NameConstant]: 136 | return gettype(ty.value) 137 | elif t in [ast.Attribute]: 138 | return gettype(ty.value)+"."+gettype(ty.attr) 139 | elif t == ast.Subscript: 140 | return gettype(ty.value)+'['+gettype(ty.slice)+']' 141 | elif t == ast.Tuple: 142 | # If we're processing a Dict[str,str] 143 | # the "str,str" part is an ast.Tuple 144 | s = '' 145 | sep = '' 146 | for e in ty.elts: 147 | s += sep + gettype(e) 148 | sep = ',' 149 | return s 150 | elif t == ast.Call: 151 | if ty.func.id == "Ref": 152 | return "%s&" % gettype(ty.args[0]) 153 | elif ty.func.id == "Const": 154 | return "%s const" % gettype(ty.args[0]) 155 | elif ty.func.id == "Move": 156 | return "%s&&" % gettype(ty.args[0]) 157 | elif ty.func.id == "Ptr": 158 | return "%s*" % gettype(ty.args[0]) 159 | else: 160 | s = ty.func.id + "<" 161 | for i in len(ty.args): 162 | if i > 0: 163 | s += "," 164 | arg = ty.args[i] 165 | s += gettype(arg) 166 | s += ">" 167 | return s 168 | elif type(ty) == str: 169 | print(ty) 170 | raise Exception("?") 171 | else: 172 | print(ty.func.id) 173 | print(ty.args,"//",dir(ty.args[0])) 174 | print(ty.args[0].s, ty.args[1].id) 175 | print("<<",ty.__class__.__name__,">>",dir(ty)) 176 | raise Exception("?") 177 | 178 | def visittree(tree,cdesc,docmap): 179 | """ 180 | Visits the AST tree for the component definition 181 | and identifies all the fields and functions with 182 | their types. 183 | """ 184 | nm = tree.__class__.__name__ 185 | if nm in ["Module"]: 186 | # tree.body is a list 187 | for k in tree.body: 188 | cdesc = visittree(k,cdesc,docmap) 189 | 190 | elif nm in ["ClassDef"]: 191 | assert cdesc is None 192 | 193 | # Create the ComponentDesc object 194 | cdesc = ComponentDesc(tree.name) 195 | for k in tree.body: 196 | cdesc = visittree(k,cdesc,docmap) 197 | 198 | elif nm in ["FunctionDef"]: 199 | args = [] 200 | code = None 201 | if tree.name in docmap.keys(): 202 | code = docmap[tree.name] 203 | for a in tree.args.args: 204 | if a.arg != "self": 205 | args += [(a.arg, gettype(a.annotation))] 206 | rtype = gettype(tree.returns) 207 | cdesc.addfunc(FuncDesc(tree.name, args, rtype, code)) 208 | 209 | # Process annotated assignments 210 | elif nm == "AnnAssign": 211 | cdesc.addfield( 212 | gettype(tree.annotation), 213 | getval(tree.target), 214 | getval(tree.value)) 215 | 216 | return cdesc 217 | 218 | def ttran_(n): 219 | "A helper for ttran in translating Python names to C++ names" 220 | if n == "np.float32": 221 | return "std::float32_t"; 222 | elif n == "np.float64": 223 | return "std::float64_t"; 224 | elif n == "np.int64": 225 | return "std::int64_t"; 226 | elif n == "str": 227 | return "std::string" 228 | elif n == "None": 229 | return "void"; 230 | elif n == "List": 231 | return "std::vector" 232 | elif n == "Dict": 233 | return "std::map" 234 | elif n == '[': 235 | return '<' 236 | elif n == ']': 237 | return '>' 238 | else: 239 | return n 240 | 241 | def ttran(n): 242 | "Translate Python names to C++ names" 243 | s = '' 244 | for i in re.finditer(r'[\w\.]+|.',n): 245 | s += ttran_(i.group(0)) 246 | return s 247 | 248 | def mkdef(n): 249 | """ 250 | Turns non-word characters into underscore 251 | and makes everything upper case. 252 | """ 253 | return re.sub(r'\W','_',n.upper()) 254 | 255 | def Component_(a,kwargs): 256 | global server_only_funcs 257 | src = inspect.getsource(a) 258 | tree = ast.parse(src) 259 | 260 | docmap = {} 261 | for k in dir(a): 262 | v = getattr(a,k) 263 | if hasattr(v,"__name__") and hasattr(v,"__doc__"): 264 | docmap[v.__name__]=v.__doc__ 265 | 266 | # Construct a ComponentDesc object 267 | c = visittree(tree,None,docmap) 268 | 269 | if "namespace" in kwargs: 270 | c.namespace = kwargs["namespace"] 271 | if "headers" in kwargs: 272 | c.headers = kwargs["headers"] 273 | if "pybind11" in kwargs: 274 | pybind11 = kwargs["pybind11"] 275 | else: 276 | pybind11 = None 277 | print(c) 278 | 279 | # Create the main header file name 280 | fname = c.cname + ".hpp" 281 | 282 | # fconst is used to make the 283 | # #ifndef .... 284 | # #define .... 285 | # macros at the top of the headers. 286 | if c.namespace is not None: 287 | fconst = mkdef(c.namespace+"::"+fname) 288 | else: 289 | fconst = mkdef(fname) 290 | 291 | with open(fname,"w") as fd: 292 | print("#ifndef %s" % fconst,file=fd) 293 | print("#define %s 1" % fconst,file=fd) 294 | 295 | # Insert headers... 296 | print(""" 297 | #include 298 | #include 299 | #include 300 | #include 301 | #include 302 | """,file=fd) 303 | for h in c.headers: 304 | print("#include <%s>" % h,file=fd) 305 | 306 | # Insert namespace declarations... 307 | if c.namespace is not None: 308 | for nm in c.namespace.split("::"): 309 | print("namespace %s {" % nm,file=fd) 310 | print("namespace server {",file=fd) 311 | 312 | # Insert start of class 313 | print(""" 314 | struct HPX_COMPONENT_EXPORT {cname} 315 | : hpx::components::component_base<{cname}> 316 | {{ 317 | /** 318 | * All internal state. 319 | */ 320 | """.format(cname=c.cname),file=fd) 321 | 322 | # Insert declared fields 323 | for k in sorted(c.fields.keys()): 324 | t,v = c.fields[k] 325 | t = ttran(t) 326 | if v == "default": 327 | print(" %s %s;" % (t,k),file=fd) 328 | elif v is None: 329 | print(" %s %s = nullptr;" % (t,k),file=fd) 330 | else: 331 | print(" %s %s = %s;" % (t,k,v),file=fd) 332 | 333 | # Insert functions 334 | print(" /* Functions */",file=fd) 335 | 336 | print(file=fd) 337 | if "__init__" not in c.funcs.keys(): 338 | # Add empty constructor and destructor if need be 339 | print(" {clazz}() {{}}".format(clazz=c.cname),file=fd) 340 | if "__del__" not in c.funcs.keys(): 341 | print(" ~{clazz}() {{}}".format(clazz=c.cname),file=fd) 342 | 343 | for k in sorted(c.funcs.keys()): 344 | func = c.funcs[k] 345 | print(file=fd) 346 | 347 | # Special case for __init__... 348 | if func.name == "__del__": 349 | if func.body is None: 350 | print(" ~%s();" % c.cname,file=fd) 351 | else: 352 | print(" ~%s() { %s }" % (c.cname,func.body),file=fd) 353 | continue 354 | elif func.name == "__init__": 355 | print(" %s(" % c.cname,end='',file=fd) 356 | 357 | # All non-special functions... 358 | else: 359 | print(" %s %s(" % (ttran(func.rettype), func.name),end='',file=fd) 360 | 361 | # Print function arguments... 362 | sep = "" 363 | for argname, argtype in func.args: 364 | print("%s%s %s" % (sep, ttran(argtype), argname),end='',file=fd) 365 | sep = ", " 366 | 367 | # End function def. 368 | if func.body is None: 369 | print(");",file=fd) 370 | else: 371 | print(") {",func.body,"}",file=fd) 372 | 373 | # If this isn't a server_only function, create a component action 374 | if func.name != "__init__": 375 | if func.name not in server_only_funcs: 376 | print(" HPX_DEFINE_COMPONENT_ACTION(%s, %s);" % 377 | (c.server_name(), func.name),file=fd) 378 | 379 | print("}; // end server code",file=fd) 380 | print(file=fd) 381 | print("} // end server namespace",file=fd) 382 | 383 | # End user supplied namespaces 384 | if c.namespace is not None: 385 | print(file=fd) 386 | for nm in c.namespace.split("::"): 387 | print("} // namespace %s" % nm,file=fd) 388 | 389 | # Register action declarations... 390 | print(file=fd) 391 | for k in sorted(c.funcs.keys()): 392 | if k == "__init__": 393 | continue 394 | if k == "__del__": 395 | continue 396 | if k in server_only_funcs: 397 | continue 398 | print("HPX_REGISTER_ACTION_DECLARATION(",file=fd) 399 | print(" %s::%s_action, %s);" % 400 | (c.server_name(),k,c.server_name()),file=fd) 401 | 402 | # Re-open user namespace for client code... 403 | print(file=fd) 404 | if c.namespace is not None: 405 | for nm in c.namespace.split("::"): 406 | print("namespace %s {" % nm,file=fd) 407 | 408 | client = """ 409 | struct {clazz} : hpx::components::client_base<{client_class},{server_class}> 410 | {{ 411 | typedef hpx::components::client_base<{client_class},{server_class}> base_type; 412 | 413 | {clazz}(hpx::future && f) 414 | : base_type(std::move(f)) 415 | {{}} 416 | 417 | {clazz}(hpx::naming::id_type && f) 418 | : base_type(std::move(f)) 419 | {{}} 420 | 421 | ~{clazz}(){{}}""".format( 422 | clazz=c.cname, 423 | client_class=c.full_name(), 424 | server_class=c.server_name()) 425 | print(client,file=fd) 426 | 427 | # Re-traverse functions so that we can 428 | # define them in the client 429 | for k in sorted(c.funcs.keys()): 430 | func = c.funcs[k] 431 | if k in server_only_funcs: 432 | continue 433 | print(file=fd) 434 | 435 | # skip init 436 | if func.name == "__init__": 437 | continue 438 | if func.name == "__del__": 439 | continue 440 | 441 | # Beginning of function through the function name... 442 | elif ttran(func.rettype).startswith("hpx::future<"): 443 | print(" %s %s(" % (ttran(func.rettype), func.name),end='',file=fd) 444 | else: 445 | print(" hpx::future<%s> %s(" % (ttran(func.rettype), func.name),end='',file=fd) 446 | 447 | # Arg types for the function... 448 | sep = "" 449 | for argname, argtype in func.args: 450 | print("%s%s %s" % (sep, ttran(argtype), argname),end='',file=fd) 451 | sep = ", " 452 | 453 | # End function prototype, begin function body... 454 | print(") {",file=fd) 455 | 456 | # Function body, just a call to async with the action... 457 | print(" return hpx::async<%s::%s_action>(this->get_id()" % 458 | (c.server_name(),k),end='',file=fd) 459 | 460 | # Each arg in the call... 461 | for argname, argtype in func.args: 462 | print(",%s" % (argname),end='',file=fd) 463 | 464 | # End of the call. 465 | print(");",file=fd) 466 | 467 | # End of function body. 468 | print(" }",file=fd) 469 | 470 | ### 471 | # Begin static version of the function call... 472 | print(" static hpx::future<%s> %s(hpx::naming::id_type& this_id" % (ttran(func.rettype), func.name),end='',file=fd) 473 | 474 | # Arg types for the static version... 475 | for argname, argtype in func.args: 476 | print(",%s %s" % (ttran(argtype), argname),end='',file=fd) 477 | 478 | # End of prototype, begin function body for static... 479 | print(") {",file=fd) 480 | 481 | # call to async... 482 | print(" return hpx::async<%s::%s_action>(this_id" % 483 | (c.server_name(),k),end='',file=fd) 484 | 485 | # args for async call... 486 | for argname, argtype in func.args: 487 | print(",%s" % (argname),end='',file=fd) 488 | 489 | # end of call... 490 | print(");",file=fd) 491 | 492 | # end of static function. 493 | print(" }",file=fd) 494 | 495 | print("}; // end client code",file=fd) 496 | 497 | # Close user namespace 498 | if c.namespace is not None: 499 | print(file=fd) 500 | for nm in c.namespace.split("::"): 501 | print("} // namespace %s" % nm,file=fd) 502 | 503 | print(file=fd) 504 | print("#endif",file=fd) 505 | 506 | # Macros for register the component and its actions 507 | fmname = c.cname + "_macros.cpp" 508 | with open(fmname,"w") as fd: 509 | print("""#include <{inc}> 510 | HPX_REGISTER_COMPONENT_MODULE(); 511 | 512 | typedef hpx::components::component< 513 | {server_class} 514 | > {clazz}_type; 515 | 516 | HPX_REGISTER_COMPONENT({clazz}_type, {clazz}); 517 | """.format(clazz=c.cname,server_class=c.server_name(),inc=fname),file=fd) 518 | 519 | for k in sorted(c.funcs.keys()): 520 | if k == "__init__": 521 | continue 522 | if k == "__del__": 523 | continue 524 | if k in server_only_funcs: 525 | continue 526 | print("""HPX_REGISTER_ACTION( 527 | {server_class}::{func}_action, {clazz}_{func}_action); 528 | """.format(clazz=c.cname,server_class=c.server_name(),func=k),file=fd) 529 | # Next, pybind11 530 | if pybind11 is None: 531 | return a 532 | if pybind11 == c.namespace: 533 | raise Exception("pybind11 cannot be the same as namespace") 534 | if pybind11 == c.cname: 535 | raise Exception("pybind11 cannot be the same as the class name") 536 | with open(pybind11+"_py11.cpp","w") as fd: 537 | print(""" 538 | #include 539 | #include 540 | #include 541 | #include <{clazz}.hpp> 542 | #include 543 | 544 | using hpx_global::start_runtime; 545 | using hpx_global::stop_runtime; 546 | 547 | namespace py = pybind11; 548 | 549 | struct {clazz}_wrapper {{ 550 | {namesp}::{clazz} *inst; 551 | 552 | {clazz}_wrapper() {{ 553 | run_hpx([&]() -> void {{ 554 | inst = new {namesp}::{clazz}( 555 | hpx::components::new_<{namesp}::server::{clazz}>(hpx::find_here())); 556 | }}); 557 | }} 558 | """.format(clazz=c.cname,namesp=c.namespace),file=fd) 559 | for k in sorted(c.funcs.keys()): 560 | if k == "__init__": 561 | continue 562 | if k == "__del__": 563 | continue 564 | if k in server_only_funcs: 565 | continue 566 | func = c.funcs[k] 567 | decl_args = get_decl_args(func) 568 | args = get_args(func) 569 | ret = ttran(func.rettype) 570 | if ret == "void": 571 | print(""" 572 | void {fname}({decl_args}) {{ 573 | return run_hpx([&]() -> void {{ 574 | inst->{fname}({args}).wait(); 575 | }}); 576 | }}""".format(fname=k,decl_args=decl_args,args=args),file=fd) 577 | else: 578 | print(""" 579 | {ret} {fname}({decl_args}) {{ 580 | return run_hpx([&]() -> {ret} {{ 581 | return inst->{fname}({args}).get(); 582 | }}); 583 | }}""".format(fname=k,decl_args=decl_args,args=args,ret=ret),file=fd) 584 | 585 | 586 | print(""" 587 | }}; 588 | 589 | PYBIND11_MODULE({pybind11}, m) {{ 590 | m.doc() = "Pybind11 interface to {namesp}::{clazz}"; 591 | m.def("start",start_runtime,"start the runtime"); 592 | m.def("stop",stop_runtime,"stop the runtime"); 593 | 594 | py::class_<{clazz}_wrapper>(m, "{clazz}") 595 | .def(py::init<>())""".format( 596 | clazz=c.cname,namesp=c.namespace,pybind11=pybind11),file=fd) 597 | for k in sorted(c.funcs.keys()): 598 | if k == "__init__": 599 | continue 600 | if k == "__del__": 601 | continue 602 | if k in server_only_funcs: 603 | continue 604 | func = c.funcs[k] 605 | decl_args = get_decl_args(func) 606 | if decl_args == "": 607 | cdecl_args = "" 608 | else: 609 | cdecl_args = ","+decl_args 610 | args = get_args(func) 611 | ret = ttran(func.rettype) 612 | kargs = { 613 | "fname":func.name, 614 | "args":args, 615 | "decl_args":decl_args, 616 | "cdecl_args":cdecl_args, 617 | "ret":ret, 618 | "clazz":c.cname, 619 | } 620 | if ret == "void": 621 | print(""" 622 | .def("{fname}", 623 | []({clazz}_wrapper c{cdecl_args}) {{ 624 | c.{fname}({args}); 625 | }})""".format(**kargs),file=fd) 626 | else: 627 | print(""" 628 | .def("{fname}", 629 | []({clazz}_wrapper c{cdecl_args}) {{ 630 | return c.{fname}({args}); 631 | }})""".format(**kargs),file=fd) 632 | print(" ;",file=fd) 633 | print("}",file=fd) 634 | return a 635 | 636 | def Component(**kwargs): 637 | global server_only_funcs, locked_funcs 638 | server_only_funcs = [] 639 | locked_funcs = [] 640 | return lambda a : Component_(a,kwargs) 641 | 642 | class basic_type: 643 | def __init__(self,name=None): 644 | if name is not None: 645 | self.full_name = name 646 | 647 | class template_type: 648 | def __init__(self,name=None): 649 | if name is not None: 650 | self.full_name = name 651 | def __getitem__(self,a): 652 | pass 653 | def __getslice__(self,*a): 654 | pass 655 | 656 | type_names = {} 657 | 658 | def create_type(name,alt=None,is_template=False): 659 | global type_names 660 | assert name is not None 661 | if alt is not None: 662 | type_names[name] = alt 663 | else: 664 | type_names[name] = name 665 | stack = inspect.stack() 666 | if 1 < len(stack): 667 | index = 1 668 | else: 669 | index = 0 670 | if is_template: 671 | stack[index].frame.f_globals[name] = template_type(name) 672 | else: 673 | stack[index].frame.f_globals[name] = basic_type(name) 674 | 675 | def locked_(m,a): 676 | global locked_funcs 677 | locked_funcs += [(a.__name__,m)] 678 | return a 679 | 680 | def locked(m): 681 | """ 682 | This describes a function that is wrapped in a lock. 683 | """ 684 | return partial(locked_,m) 685 | 686 | def server_only(a): 687 | """ 688 | The server_only function annotation identifies a method that 689 | will not be exported by the component. 690 | """ 691 | global server_only_funcs 692 | server_only_funcs += [a.__name__] 693 | return a 694 | 695 | def Const(_): 696 | "Used to identify const types" 697 | pass 698 | 699 | def Ref(_): 700 | "Used to identify references" 701 | pass 702 | def Ptr(_): 703 | "Used to identify pointer types" 704 | pass 705 | 706 | 707 | #id_type = basic_type("hpx::naming::id_type") 708 | #smap = template_type("std::map") 709 | create_type("id_type",alt="hpx::naming::id_type") 710 | create_type("smap",alt="std::map",is_template=True) 711 | create_type("myvec",alt="std::vector",is_template=True) 712 | create_type("Bool",alt="bool") 713 | create_type("Boolean",alt="bool") 714 | 715 | default = object() 716 | 717 | ######### 718 | # Component definitions 719 | --------------------------------------------------------------------------------