{"id":13828058,"url":"https://github.com/lisachenko/z-engine","last_synced_at":"2025-05-16T14:05:39.452Z","repository":{"id":35982286,"uuid":"206517572","full_name":"lisachenko/z-engine","owner":"lisachenko","description":":zap: PHP Engine Direct API","archived":false,"fork":false,"pushed_at":"2022-10-21T13:11:38.000Z","size":255,"stargazers_count":452,"open_issues_count":10,"forks_count":22,"subscribers_count":20,"default_branch":"master","last_synced_at":"2025-05-14T17:49:27.697Z","etag":null,"topics":["core","ffi","low-level","native-structures","php","reflection-api","z-engine"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lisachenko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-05T08:48:12.000Z","updated_at":"2025-03-13T04:39:55.000Z","dependencies_parsed_at":"2022-09-16T01:34:23.171Z","dependency_job_id":null,"html_url":"https://github.com/lisachenko/z-engine","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lisachenko%2Fz-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lisachenko%2Fz-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lisachenko%2Fz-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lisachenko%2Fz-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lisachenko","download_url":"https://codeload.github.com/lisachenko/z-engine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254544146,"owners_count":22088807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["core","ffi","low-level","native-structures","php","reflection-api","z-engine"],"created_at":"2024-08-04T09:02:30.406Z","updated_at":"2025-05-16T14:05:39.431Z","avatar_url":"https://github.com/lisachenko.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"Z-Engine library\n-----------------\n\n[![Build Status](https://img.shields.io/travis/com/lisachenko/z-engine/master)](https://travis-ci.org/lisachenko/z-engine)\n[![GitHub release](https://img.shields.io/github/release/lisachenko/z-engine.svg)](https://github.com/lisachenko/z-engine/releases/latest)\n[![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%207.4-8892BF.svg)](https://php.net/)\n[![License](https://img.shields.io/packagist/l/lisachenko/z-engine.svg)](https://packagist.org/packages/lisachenko/z-engine)\n\nHave you ever dreamed about mocking a final class or redefining final method? Or maybe have an ability to work with existing classes in runtime?\n`Z-Engine` is a PHP7.4 library that provides an API to PHP. Forget about all existing limitations and use this library to transform your existing code in runtime by declaring new methods, adding new interfaces to the classes and even installing your own system hooks, like opcode compilation, object initalization and much more.\n\n**:warning: DO NOT USE IT IN PRODUCTION UNTIL 1.0.0!**\n\nHow it works?\n------------\n\nAs you know, PHP version 7.4 contains a new feature, called [FFI](https://www.php.net/manual/en/book.ffi.php). It allows the loading of shared libraries (.dll or .so), calling of C functions and accessing of C data structures in pure PHP, without having to have deep knowledge of the Zend extension API, and without having to learn a third \"intermediate\" language.\n\n`Z-Engine` uses FFI to access internal structures of... PHP itself. This idea was so crazy to try, but it works! `Z-Engine` loads definition of native PHP structures, like `zend_class_entry`, `zval`, etc and manipulates them in runtime. Of course, it is dangerous, since `FFI` allows to work with structures on a very low level. Thus, you should expect segmentation faults, memory leaks and other bad things.\n\nPre-requisites and initialization\n--------------\n\nAs this library depends on `FFI`, it requires PHP\u003e=7.4 and `FFI` extension to be enabled.\nIt should work in CLI mode without any troubles, whereas for web mode `preload` mode should be activated.\nAlso, current version is limited to x64 non-thread-safe versions of PHP.\n\nTo install this library, simply add it via `composer`:\n```bash\ncomposer require lisachenko/z-engine\n```\nTo activate a `preload` mode, please add `Core::preload()` call into your script, specified by `opcache.preload`. This call will be done during the server preload and will be used by library to bypass unnecessary C headers processing during each request.\n\nNext step is to init library itself with short call to the `Core::init()`:\n```php\nuse ZEngine\\Core;\n\ninclude __DIR__.'/vendor/autoload.php';\n\nCore::init();\n```\n\nNow you can test it with following example:\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nuse ZEngine\\Reflection\\ReflectionClass;\n\ninclude __DIR__.'/vendor/autoload.php';\n\nfinal class FinalClass {}\n\n$refClass = new ReflectionClass(FinalClass::class);\n$refClass-\u003esetFinal(false);\n\neval('class TestClass extends FinalClass {}'); // Should be created\n```\n\nTo have an idea, what you can do with this library, please see library tests as an example.\n\nReflectionClass\n------------\n\nLibrary provides and extension for classic reflection API to manipulate internal structure of class via `ReflectionClass`:\n  - `setFinal(bool $isFinal = true): void` Makes specified class final/non-final\n  - `setAbstract(bool $isAbstract = true): void` Makes specified class abstract/non-abstract. Even if it contains non-implemented methods from interface or abstract class.\n  - `setStartLine(int $newStartLine): void` Updates meta-information about the class start line\n  - `setEndLine(int $newEndLine): void` Updates meta-information about the class end line\n  - `setFileName(string $newFileName): void` Sets a new filename for this class\n  - `setParent(string $newParent)` \\[WIP\\] Configures a new parent class for this one\n  - `removeParentClass(): void` \\[WIP\\] Removes the parent class\n  - `removeTraits(string ...$traitNames): void` \\[WIP\\] Removes existing traits from the class\n  - `addTraits(string ...$traitNames): void` \\[WIP\\] Adds new traits to the class\n  - `removeMethods(string ...$methodNames): void` Removes list of methods from the class\n  - `addMethod(string $methodName, \\Closure $method): ReflectionMethod` Adds a new method to the class\n  - `removeInterfaces(string ...$interfaceNames): void` Removes a list of interface names from the class\n  - `addInterfaces(string ...$interfaceNames): void` Adds a list of interfaces to the given class.\n\nBeside that, all methods that return `ReflectionMethod` or `ReflectionClass` were decorated to return an extended object with low-level access to native structures.\n\nReflectionMethod\n-------------\n\n `ReflectionMethods` contains methods to work with a definition of existing method:\n\n   - `setFinal(bool $isFinal = true): void` Makes specified method final/non-final\n   - `setAbstract(bool $isAbstract = true): void` Makes specified method abstract/non-abstract.\n   - `setPublic(): void` Makes specified method public\n   - `setProtected(): void` Makes specified method protected\n   - `setPrivate(): void` Makes specified method private\n   - `setStatic(bool $isStatic = true): void` Declares method as static/non-static\n   - `setDeclaringClass(string $className): void` Changes the declaring class name for this method\n   - `setDeprecated(bool $isDeprecated = true): void` Declares this method as deprecated/non-deprecated\n   - `redefine(\\Closure $newCode): void` Redefines this method with a closure definition\n   - `getOpCodes(): iterable`: \\[WIP\\] Returns the list of opcodes for this method\n\nObjectStore API\n-------------\n\nEvery object in PHP has it's own unique identifier, which can be received via `spl_object_id($object)`. Sometimes\nwe are looking for the way to get an object by it's identifier. Unfortunately, PHP doesn't provide such an API, whereas\ninternally there is an instance of `zend_objects_store` structure which is stored in the global `executor_globals`\nvariable (aka EG).\n\nThis library provides an `ObjectStore` API via `Core::$executor-\u003eobjectStore` which implements an `ArrayAccess` and\n`Countable` interface. This means that you can get any existing object by accessing this store with object handle:\n\n```php\nuse ZEngine\\Core;\n\n$instance = new stdClass();\n$handle   = spl_object_id($instance);\n\n$objectEntry = Core::$executor-\u003eobjectStore[$handle];\nvar_dump($objectEntry);\n```\n\nObject Extensions API\n---------------------\n\nWith the help of `z-engine` library it is possible to overload standard operators for your classes without diving deep\ninto the PHP engine implementation. For example, let's say you want to define native matrix operators and use it:\n\n```php\n\u003c?php\n\nuse ZEngine\\ClassExtension\\ObjectCastInterface;\nuse ZEngine\\ClassExtension\\ObjectCompareValuesInterface;\nuse ZEngine\\ClassExtension\\ObjectCreateInterface;\nuse ZEngine\\ClassExtension\\ObjectCreateTrait;\nuse ZEngine\\ClassExtension\\ObjectDoOperationInterface;\n\nclass Matrix implements\n    ObjectCreateInterface,\n    ObjectCompareValuesInterface,\n    ObjectDoOperationInterface,\n    ObjectCastInterface\n{\n    use ObjectCreateTrait;\n\n    // ...\n}\n$a = new Matrix([10, 20, 30]);\n$b = new Matrix([1, 2, 3]);\n$c = $a + $b; // Matrix([11, 22, 33])\n$c *= 2;      // Matrix([22, 44, 66])\n```\n\nThere are two ways of activating custom handlers.\nFirst way is to implement several system interfaces like\n`ObjectCastInterface`, `ObjectCompareValuesInterface`, `ObjectCreateInterface` and `ObjectDoOperationInterface`. After\nthat you should create an instance of `ReflectionClass` provided by this package and call `installExtensionHandlers`\nmethod to install extensions:\n\n```php\nuse ZEngine\\Reflection\\ReflectionClass as ReflectionClassEx;\n\n// ... initialization logic\n\n$refClass = new ReflectionClassEx(Matrix::class);\n$refClass-\u003einstallExtensionHandlers();\n```\n\nif you don't have an access to the code (eg. vendor), then you can still have an ability to define custom handlers.\nYou need to define callbacks as closures explicitly and assign them via `set***Handler()` methods in the\n`ReflectionClass`.\n\n```php\nuse ZEngine\\ClassExtension\\ObjectCreateTrait;\nuse ZEngine\\Reflection\\ReflectionClass as ReflectionClassEx;\n\n$refClass = new ReflectionClassEx(Matrix::class);\n$handler  = Closure::fromCallable([ObjectCreateTrait::class, '__init']);\n$refClass-\u003esetCreateObjectHandler($handler);\n$refClass-\u003esetCompareValuesHandler(function ($left, $right) {\n    if (is_object($left)) {\n        $left = spl_object_id($left);\n    }\n    if (is_object($right)) {\n        $right = spl_object_id($right);\n    }\n\n    // Just for example, object with bigger object_id is considered bigger that object with smaller object_id\n    return $left \u003c=\u003e $right;\n});\n```\n\nLibrary provides following interfaces:\n\nFirst one is `ObjectCastInterface` which provides a hook for handling casting a class instance to scalars. Typical\nexamples are following: 1) explicit `$value = (int) $objectInstance` or implicit: `$value = 10 + $objectInstance;` in\nthe case when `do_operation` handler is not installed. Please note, that this handler doesn't handle casting to `array`\ntype as it is implemented in a different way.\n\n```php\n\u003c?php\nuse ZEngine\\ClassExtension\\Hook\\CastObjectHook;\n\n/**\n * Interface ObjectCastInterface allows to cast given object to scalar values, like integer, floats, etc\n */\ninterface ObjectCastInterface\n{\n    /**\n     * Performs casting of given object to another value\n     *\n     * @param CastObjectHook $hook Instance of current hook\n     *\n     * @return mixed Casted value\n     */\n    public static function __cast(CastObjectHook $hook);\n}\n```\nTo get the type of casting, you should check `$hook-\u003egetCastType()` method which will return the integer value of type.\nPossible values are declared as public constants in the `ReflectionValue` class. For example `ReflectionValue::IS_LONG`.\n\nNext `ObjectCompareValuesInterface` interface is used to control the comparison logic. For example, you can compare\ntwo objects or even compare object with scalar values: `if ($object \u003e 10 || $object \u003c $anotherObject)`\n\n```php\n\u003c?php\nuse ZEngine\\ClassExtension\\Hook\\CompareValuesHook;\n\n/**\n * Interface ObjectCompareValuesInterface allows to perform comparison of objects\n */\ninterface ObjectCompareValuesInterface\n{\n    /**\n     * Performs comparison of given object with another value\n     *\n     * @param CompareValuesHook $hook Instance of current hook\n     *\n     * @return int Result of comparison: 1 is greater, -1 is less, 0 is equal\n     */\n    public static function __compare(CompareValuesHook $hook): int;\n}\n```\nHandler should check arguments which can be received by calling `$hook-\u003egetFirst()` and `$hook-\u003egetSecond()` methods\n(one of them should return an instance of your class) and return integer result -1..1. Where\n1 is greater, -1 is less and 0 is equal.\n\nThe interface `ObjectDoOperationInterface` is the most powerful one because it gives you control over math operators\napplied to your object (such as ADD, SUB, MUL, DIV, POW, etc).\n\n```php\n\u003c?php\nuse ZEngine\\ClassExtension\\Hook\\DoOperationHook;\n\n/**\n * Interface ObjectDoOperationInterface allows to perform math operations (aka operator overloading) on object\n */\ninterface ObjectDoOperationInterface\n{\n    /**\n     * Performs an operation on given object\n     *\n     * @param DoOperationHook $hook Instance of current hook\n     *\n     * @return mixed Result of operation value\n     */\n    public static function __doOperation(DoOperationHook $hook);\n}\n```\nThis handler receives an opcode (see `OpCode::*` constants) via `$hook-\u003egetOpcode()` and two arguments (one of them is\nan instance of class) via `$hook-\u003egetFirst()` and `$hook-\u003egetSecond()` and returns a value for that operation.\nIn this handler you can return a new instance of your object to have a chain of immutable instances of objects.\n\nImportant reminder: you **MUST** install the `create_object` handler first in order to install hooks in runtime. Also\nyou can not install the `create_object` handler for the object if it is internal one.\n\nThere is one extra method called `setInterfaceGetsImplementedHandler` which is useful for installing special handler for\ninterfaces. The `interface_gets_implemented` callback uses the same memory slot as `create_object` handler for object,\nand will be called each time when any class will implement this interface. This gives interesting options for\nautomatic class extensions registration, for example, if a class implements the `ObjectCreateInterface` then\nautomatically call `ReflectionClass-\u003einstallExtensionHandlers()` for it in callback.\n\nAbstract Syntax Tree API\n--------------\n\nAs you know, PHP7 uses an abstract syntax tree for working with abstract model of source code to simplify future\ndevelopment of language syntax. Unfortunately, this information is not provided back to the userland level. There are\nseveral PHP extensions like [nikic/php-ast](https://github.com/nikic/php-ast) and\n[sgolemon/astkit](https://github.com/sgolemon/astkit/) that provide low-level bindings to the underlying AST structures.\n`Z-Engine` provides access to the AST via `Compiler::parseString(string $source, string $fileName = '')` method. This\nmethod will return a top-level node of tree that implements `NodeInterface`. PHP has four types of AST nodes, they are:\ndeclaration node (classes, methods, etc), list node (can contain any number of children nodes), simple node (contains\nup to 4 children nodes, depending of type) and special value node class that can store any value in it (typically string\nor numeric).\n\nHere are an example of parsing simple PHP code:\n\n```php\nuse ZEngine\\Core;\n\n$ast = Core::$compiler-\u003eparseString('echo \"Hello, world!\", PHP_EOL;', 'hi.php');\necho $ast-\u003edump();\n```\nOutput will be like that:\n```\n   1: AST_STMT_LIST\n   1:   AST_STMT_LIST\n   1:     AST_ECHO\n   1:       AST_ZVAL string('Hello, world!')\n   1:     AST_ECHO\n   1:       AST_CONST\n   1:         AST_ZVAL attrs(0001) string('PHP_EOL')\n```\n\nNode provides simple API to mutate children nodes via call to the `Node-\u003ereplaceChild(int $index, ?Node $node)`. You can\ncreate your own nodes in runtime or use a result from `Compiler::parseString(string $source, string $fileName = '')` as\nreplacement for your code.\n\nModifying the Abstract Syntax Tree\n--------------\nWhen PHP 7 compiles PHP code it converts it into an abstract syntax tree (AST) before finally generating Opcodes that\nare persisted in Opcache. The `zend_ast_process` hook is called for every compiled script and allows you to modify the\nAST after it is parsed and created.\n\nTo install the `zend_ast_process` hook, make a static call to the `Core::setASTProcessHandler(Closure $callback)`\nmethod that accepts a callback which will be called during AST processing and will receive a `AstProcessHook $hook` as\nan argument. You can access top-level node item via `$hook-\u003egetAST(): NodeInterface` method.\n\n```php\nuse ZEngine\\Core;\nuse ZEngine\\System\\Hook\\AstProcessHook;\n\nCore::setASTProcessHandler(function (AstProcessHook $hook) {\n    $ast = $hook-\u003egetAST();\n    echo \"Parsed AST:\", PHP_EOL, $ast-\u003edump();\n    // Let's modify Yes to No )\n    echo $ast-\u003egetChild(0)-\u003egetChild(0)-\u003egetChild(0)-\u003egetValue()-\u003esetNativeValue('No');\n});\n\neval('echo \"Yes\";');\n\n// Parsed AST:\n//    1: AST_STMT_LIST\n//    1:   AST_STMT_LIST\n//    1:     AST_ECHO\n//    1:       AST_ZVAL string('Yes')\n// No\n```\n\nYou can see that result of evaluation is changed from \"Yes\" to \"No\" because we have adjusted given AST in our callback.\nBut be aware, that this is one of the most complicated hooks to use, because it requires perfect understanding of the\nAST possibilities. Creating an invalid AST here can cause weird behavior or crashes.\n\nCreating PHP extensions in runtime\n--------------\nThe most interesting part of Z-Engine library is creating your own PHP extensions in PHP language itself.\nYou do not have to spend a lot of time learning the C language; instead, you can use the ready-made API to create\nyour own extension module from PHP itself!\n\nOf course, not everything is possible to implement as an extension in PHP, for example, changing the parser syntax\nor changing the logic of opcache - for this you will have to delve into the code of the engine itself.\n\nLet's make an example a module with global variables, an analog of apcu, so that these variables are not cleared\nafter the request is completed. It is believed that PHP has the concept of share nothing and therefore can’t survive\nthe boundary of the request, since at the time of completion of the request PHP will automatically free all allocated\nmemory for objects. However, PHP itself can work with global variables, and they are stored inside loaded modules by\nthe pointer `zend_module_entry.globals_ptr`.\n\nTherefore, if we can register the module in PHP and allocate global memory for it, PHP will not clear it, and our\nmodule will be able to survive the boundary of the request.\n\nTechnically, every module is represented by following structure:\n\n```\nstruct _zend_module_entry {\n    unsigned short size;\n    unsigned int zend_api;\n    unsigned char zend_debug;\n    unsigned char zts;\n    const struct _zend_ini_entry *ini_entry;\n    const struct _zend_module_dep *deps;\n    const char *name;\n    const struct _zend_function_entry *functions;\n    int (*module_startup_func)(int type, int module_number);\n    int (*module_shutdown_func)(int type, int module_number);\n    int (*request_startup_func)(int type, int module_number);\n    int (*request_shutdown_func)(int type, int module_number);\n    void (*info_func)(zend_module_entry *zend_module);\n    const char *version;\n    size_t globals_size;\n#ifdef ZTS\n    ts_rsrc_id* globals_id_ptr;\n#endif\n#ifndef ZTS\n    void* globals_ptr;\n#endif\n    void (*globals_ctor)(void *global);\n    void (*globals_dtor)(void *global);\n    int (*post_deactivate_func)(void);\n    int module_started;\n    unsigned char type;\n    void *handle;\n    int module_number;\n    const char *build_id;\n};\n```\nYou can see that we can define several callbacks and there are several fields with meta-information about zts, debug,\nAPI version, etc that are used by PHP to check if this module can be loaded for current environment.\n\nFrom PHP side, you should extend your module class from the `AbstractModule` class that contains general logic of\nmodule registration and startup and implement all required method from the `ModuleInterface`.\n\nLet's have a look at our simple module:\n```php\nuse ZEngine\\EngineExtension\\AbstractModule;\n\nclass SimpleCountersModule extends AbstractModule\n{\n    /**\n     * Returns the target thread-safe mode for this module\n     *\n     * Use ZEND_THREAD_SAFE as default if your module does not depend on thread-safe mode.\n     */\n    public static function targetThreadSafe(): bool\n    {\n        return ZEND_THREAD_SAFE;\n    }\n\n    /**\n     * Returns the target debug mode for this module\n     *\n     * Use ZEND_DEBUG_BUILD as default if your module does not depend on debug mode.\n     */\n    public static function targetDebug(): bool\n    {\n        return ZEND_DEBUG_BUILD;\n    }\n\n    /**\n     * Returns the target API version for this module\n     *\n     * @see zend_modules.h:ZEND_MODULE_API_NO\n     */\n    public static function targetApiVersion(): int\n    {\n        return 20190902;\n    }\n\n    /**\n     * Returns true if this module should be persistent or false if temporary\n     */\n    public static function targetPersistent(): bool\n    {\n        return true;\n    }\n\n    /**\n     * Returns globals type (if present) or null if module doesn't use global memory\n     */\n    public static function globalType(): ?string\n    {\n        return 'unsigned int[10]';\n    }\n}\n```\nOur `SimpleCountersModule` declares that it will use array of 10 unsigned ints. It also provides some information about\nrequired environment (debug/zts/API version). Important option is to mark our module persistent by returning true from\n`targetPersistent()` method. And now we are ready to register it and use it:\n\n```php\n$module = new SimpleCountersModule();\nif (!$module-\u003eisModuleRegistered()) {\n    $module-\u003eregister();\n    $module-\u003estartup();\n}\n\n$data = $module-\u003egetGlobals();\nvar_dump($data);\n```\nNote, that on subsequent requests module will be registered, this is why you should not call register twice.\nWhat is really cool is that any changes in module globals are **true globals**! They will be **preserved** between\nrequests. Try to update each item to see that values in our array are increasing between requests:\n\n```php\n$index        = mt_rand(0, 9); // If you have several workers, you should use worker pid to avoid race conditions\n$data[$index] = $data[$index] + 1; // We are increasing global counter by one\n\n/* Example of var_dump after several requests...\nobject(FFI\\CData:uint32_t[10])#35 (10) {\n  [0]=\u003e\n  int(1)\n  [1]=\u003e\n  int(1)\n  [2]=\u003e\n  int(1)\n  [3]=\u003e\n  int(3)\n  [4]=\u003e\n  int(1)\n  [5]=\u003e\n  int(1)\n  [6]=\u003e\n  int(1)\n  [7]=\u003e\n  int(2)\n  [8]=\u003e\n  int(2)\n  [9]=\u003e\n  int(2)\n}*/\n```\n\nOf course, module can declare any complex structure for globals and use it as required. If module requires some\ninitialization, then you can implement the `ControlModuleGlobalsInterface` in your module and this callback will be\ncalled during module startup procedure. This may be useful for registration of additional hooks, class extensions, etc\nor for global variable initialization (filling it with predefined values, restoring state from DB/filesystem/etc)\n\nCode of Conduct\n--------------\n\nThis project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md).\nBy participating, you are expected to uphold this code.\nPlease report any unacceptable behavior.\n\nLicense\n-------\n\nThis library is licensed under the [**MIT** license](LICENSE).\n\nCreating and maintaining this library is endless hard work for me.\nThat's why there is _one_ simple requirement for you: please give _something_ back to the world.\nWhether that's code _or_ financial support for this project is entirely up to you.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flisachenko%2Fz-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flisachenko%2Fz-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flisachenko%2Fz-engine/lists"}